Python 多线程编程

来源:互联网 发布:数据科学入门 mobi 编辑:程序博客网 时间:2024/06/08 04:37

说到多线程,那不得不提进程,那进程是什么,线程是什么,多线程又是什么,为什么要使用多线程,带着这些疑问?,我们来一起学习python 多线程。

1.进程和线程

什么是进程?
      进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
      通俗的说进程就是操作系统中正在运行的exe程序,进程间切换开销较大,一个进程可以包含1或n个线程。
      多进程是指操作系统能同时运行多个任务(程序)。

什么是线程?
      线程可以理解为进程中独立运行的子任务,线程间切换开销较小。多线程是指在同一程序中有多个顺序流在执行(程序),线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
      同时:“同时”执行是人的感觉,在线程之间实际上是轮换执行
      比如:QQ.exe在运行时就有很多的子任务在同时运行,有下载文件线程、发送表情线程、好友视频线程等,这些不同的任务或功能都在同时运行,其中每一项任务就可以理解为一个线程。

为什么使用多线程?
      如下图中所示,在单任务环境中,任务1在等待远程服务器返回数据,以便进行后期处理,这时CPU一直处于等待状态,一直在”空运行”。任务2需要等待任务1执行完成,即等待10秒后才可以执行,虽然运行任务2只用时1秒,所以在单任务环境中任务2会等待较长时间后才能执行,系统运行效率不高。
      而在多任务环境中,任务1和任务2之间可以进行切换,任务2无需等待太长时间,这样系统的运行效率就会提高。

这里写图片描述

      单任务特点:排队执行,即同步。如:在cmd中输入一条命令后,必须等待这条命令执行完成后才可以执行下一条命令。
      多任务特点:使用多线程,即异步,线程在被调用执行的顺序是随机的。

2.Python 中的多线程

1.使用线程的两种方式

Python中使用线程有两种方式:函数或者用类来包装线程对象。

线程使用方式一(函数):

import threadingimport timedef print_time(threadName, delay):    count = 0    while count < 5:        time.sleep(delay)        count += 1        print("%s: %s" % (threadName, time.ctime(time.time())))# 启动线程方式一:使用threading模块中Thread类的start()方法t1 = threading.Thread(target=print_time, args=("Thread-1", 1))t2 = threading.Thread(target=print_time, args=("Thread-2", 2))t1.start()t2.start()# 启动线程方式二:使用threading模块中的_start_new_thread()方法# threading._start_new_thread(print_time, ('Thread-1', 1))# threading._start_new_thread(print_time, ('Thread-2', 2))# while 1:#     pass

运行结果:
Thread-1: Sat Jul 1 20:18:33 2017
Thread-2: Sat Jul 1 20:18:34 2017
Thread-1: Sat Jul 1 20:18:34 2017
Thread-1: Sat Jul 1 20:18:35 2017
Thread-2: Sat Jul 1 20:18:36 2017
Thread-1: Sat Jul 1 20:18:36 2017
Thread-1: Sat Jul 1 20:18:37 2017
Thread-2: Sat Jul 1 20:18:38 2017
Thread-2: Sat Jul 1 20:18:40 2017
Thread-2: Sat Jul 1 20:18:42 2017

线程使用方式二(类):

import threadingimport timeclass MyThread(threading.Thread):    def __init__(self, threadName, delay):        # 注意:一定要显式的调用父类的初始化函数        super(MyThread, self).__init__()         self.threadName = threadName        self.delay = delay    def run(self):        count = 0        while count < 5:            count += 1            time.sleep(self.delay)            print("%s: %s" % (self.threadName, time.ctime(time.time())))t1 = MyThread("Thread-1", 1)t2 = MyThread("Thread-2", 2)t1.start()t2.start()

线程通过类包装对象的方式实现必须在线程类的初始化函数中的第一行显示的调用父类的初始化函数,如果不调用,会出现如下异常:
RuntimeError: thread.init() not called

运行结果:
Thread-1: Sat Jul 1 20:23:45 2017
Thread-1: Sat Jul 1 20:23:46 2017
Thread-2: Sat Jul 1 20:23:46 2017
Thread-1: Sat Jul 1 20:23:47 2017
Thread-1: Sat Jul 1 20:23:48 2017
Thread-2: Sat Jul 1 20:23:48 2017
Thread-1: Sat Jul 1 20:23:49 2017
Thread-2: Sat Jul 1 20:23:50 2017
Thread-2: Sat Jul 1 20:23:52 2017
Thread-2: Sat Jul 1 20:23:54 2017

2.python 多线程

Python3 中有两个线程模块,分别为_thread 和 threading,它们都提供了对线程的支持,区别是_thread提供了低级别、原始的线程和一个简单的锁,想比于threading功能较有限,threading除了提供了_thread 中的所有方法外,还提供了其它方法,如下

threading.currentThread(): 返回当前的线程变量threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

threading 模块中常用的类及方法如下

类 方法 Thread start()-启动线程,join()-加入新线程 Lock acquire()-加锁,release()-释放锁 Rlock acquire()-加锁,release()-释放锁 Condition acquire()-加锁,release()-释放锁,wait()-等待,notify()-唤醒,notify_all()-唤醒所有 Event set()-等同于notify(),clear()-等同于notify_all() time sleep()-睡眠 timer Timer(interval, function, args=[], kwargs={})——interval指定时间,function为要执行的方法,args=[], kwargs={}为方法的参数


Lock 和 Rlock 详解

在threading模块中定义了两种不同类型的锁threading.Lock和threading.RLock,Lock类和Rlock类中都有两个方法,分别为:加锁acquire()和release()释放锁。示例如下

import threadinglock = threading.Lock()  # Lock对象lock.acquire()lock.acquire()  # 产生了死琐,Lock为不可重入锁,只能调用一次acquirelock.release()lock.release()
import threadingrLock = threading.RLock()  # RLock对象rLock.acquire()rLock.acquire()  # 可重入锁,在同一线程内,程序不会堵塞rLock.release()rLock.release()

这两种类型的锁主要区别是:Rlock允许在同一线程中被多次加锁(acquire),也就是说Rlock可重入锁,而Lock是不允许这种情况。需要注意的是如果使用可重入锁Rlock,那么加锁(acquire)和释放锁(release)一定是成对出现的,即调了n次的acquire,必须调用n次的release才能释放所占用的锁。

acquire():加锁,使线程进入同步阻塞状态
release():释放锁,释放锁前该线程必须已被锁定,否则会抛出异常

Condiftion 详解

threading.Condiftion 可以理解为一把高级的锁,它提供了比Lock、Rlock更高级的功能,允许我们能够控制复杂的线程同步问题threading.Condiftion 内部维护一个锁对象(Rlock),可以在创建Condiftion 对象的时候将锁对象作为参数转入。

Condition也提供了acquire, release方法,其含义与琐的acquire, release方法一致,其实它只是简单的调用内部琐对象的对应的方法而已。

Condition还提供了如下方法(特别要注意:这些方法只有在占用琐(acquire)之后才能调用,否则将会报RuntimeError异常)
Condition.wait([timeout]): 释放内部所占用的锁,同时线程被挂起。直到被唤醒或超时(设置了超时时间)后线程重新占用锁,然后程序继续往下执行。
Condition.notify():唤醒一个挂起的线程,(如果存在挂起的线程)。注意:notify()方法不会释放所占用的琐。
Condition.notify_all()唤醒所有挂起的线程(如果存在挂起的线程)。注意:notify_all()方法不会释放所占用的琐。

3.python 多线程同步应用案例

案例描述:
共有四个和尚
其中一个和尚负责做馒头
其他三个和尚吃馒头
要求:
当做馒头的时候不能吃馒头
当吃馒头的时候不能做馒头
馒头上线只能是30个
在吃馒头的时候不能出现一个馒头被多个和尚吃
不能出现吃的时候和尚吃出异常来(如一个和尚永远也吃不到,或者一个和尚吃了一个不存在的馒头)

此案例是一个典型的生产者消费者模式,通俗的说用来产生数据的模块(此处的模块是广义的,可以是类、函数、线程、进程等)就是生产者,而处理数据的模块就是消费者。

针对此案例,做馒头的和尚为生产者,吃馒头的和尚为消费者,生产者和消费者各一线程,双方围绕馒头产生线程同步问题,首先是一个和尚做馒头,而接下来是三个和尚吃馒头。代码如下

方法一:使用函数实现

import threadingimport timemantous = []    # 用于存放馒头# 创建两个锁,锁a为和尚、锁b为伙夫a = threading.Lock()b = threading.Lock()ca = threading.Condition(a)cb = threading.Condition(b)# 伙夫的任务函数def huofu(name):    time.sleep(1)       # 休眠1秒,等待4个线程全部启动(可能会出现4个和尚还没有全部等待,伙夫就开始蒸馒头了)    while True:        ca.acquire()    # 上锁        for i in range(1, 31):            mantous.append("第{0}个馒头".format(i))            print("做好第{0}个馒头".format(i))            time.sleep(0.1)     # 模拟蒸馒头的过程        ca.notify_all()     # 伙夫蒸完馒头后,唤醒和尚        ca.release()        # 释放锁        print("{0}去等到".format(name))        cb.acquire()        cb.wait()           # 蒸完馒头后等待,等待和尚唤醒        cb.release()# 吃货的任务函数def chihuo(name):    ca.acquire()    ca.wait()   # 伙夫在唤醒前吃货必须是等待的状态    ca.release()    # 加锁,和尚排队获取馒头,吃馒头    while True:        m = None    # 每次和尚获取馒头前先把馒头置为空        ca.acquire()        if len(mantous) != 0:            m = mantous.pop()        else:            print("没有馒头了!")            cb.acquire()            cb.notify()     # 没有馒头了,唤醒伙夫            cb.release()            ca.wait()       # 吃货开始等待        ca.release()        if m is not None:            print("{0}吃{1}".format(name, m))            time.sleep(1)# 启动线程方式一# threading._start_new_thread(huofu, ('大头和尚', ))# threading._start_new_thread(chihuo, ('白眉和尚', ))# threading._start_new_thread(chihuo, ('牛鼻子和尚', ))# threading._start_new_thread(chihuo, ('花和尚', ))# 启动线程方式二t1 = threading.Thread(target=huofu, args=('大头和尚', ))t2 = threading.Thread(target=chihuo, args=('白眉和尚', ))t3 = threading.Thread(target=chihuo, args=('牛鼻子和尚', ))t4 = threading.Thread(target=chihuo, args=('花和尚', ))t1.start()t2.start()t3.start()t4.start()input()

方式二:使用类来包装线程对象

import threadingimport timemantous = []    # 用于存放馒头# 创建两个锁,锁a为和尚、锁b为伙夫a = threading.Lock()b = threading.Lock()ca = threading.Condition(a)cb = threading.Condition(b)# 伙夫class Chuofu(threading.Thread):    def __init__(self, name):        # threading.Thread.__init__(self)        super(Chuofu, self).__init__()  # 如果没有此行,会报错:Thread.__init__() not called(即函数的初始方法没有回调,且位置必须在构造方法的第一行)        self.name = name    def run(self):        time.sleep(1)   # 休眠1秒,等待4个线程全部启动(可能会出现4个和尚还没有全部等待,伙夫就开始蒸馒头了)        while True:            ca.acquire()    # 上锁            for i in range(1, 31):                mantous.append("第{0}个馒头".format(i))                print("做好第{0}个馒头".format(i))                time.sleep(0.1)     # 模拟蒸馒头的过程            print("馒头做好,叫醒吃货")            ca.notify_all()     # 伙夫蒸完馒头后,唤醒和尚            ca.release()    # 释放锁            print("{0}去等到".format(self.name))            cb.acquire()            cb.wait()      # 蒸完馒头后等待,等待和尚唤醒            cb.release()# 吃货class Cchihuo(threading.Thread):    def __init__(self, name):        threading.Thread.__init__(self)     # 如果没有此行,会报错:Thread.__init__() not called(即函数的初始方法没有回调,且位置必须在构造方法的第一行)        self.name = name    def run(self):        ca.acquire()        ca.wait()   # 伙夫在唤醒前吃货必须是等待的状态        ca.release()        while True:            m = None    # 每次和尚获取馒头前先把馒头置为空            ca.acquire()            if len(mantous) != 0:                m=mantous.pop()            else:                print("没馒头了")                cb.acquire()                cb.notify() # 唤醒伙夫,开始蒸馒头                cb.release()                ca.wait()   # 吃货开始等待            ca.release()            if m is not None:                print("{0}吃{1}".format(self.name, m))                time.sleep(1)Chuofu("大头和尚").start()Cchihuo('百媚和尚').start()Cchihuo('牛鼻子和尚').start()Cchihuo('花和尚和尚').start()input()

运行结果:方式一和方式二运行结果相同
做好第1个馒头
做好第2个馒头
做好第3个馒头
做好第4个馒头
做好第5个馒头
做好第6个馒头
做好第7个馒头
做好第8个馒头
做好第9个馒头
做好第10个馒头
做好第11个馒头
做好第12个馒头
做好第13个馒头
做好第14个馒头
做好第15个馒头
做好第16个馒头
做好第17个馒头
做好第18个馒头
做好第19个馒头
做好第20个馒头
做好第21个馒头
做好第22个馒头
做好第23个馒头
做好第24个馒头
做好第25个馒头
做好第26个馒头
做好第27个馒头
做好第28个馒头
做好第29个馒头
做好第30个馒头
馒头做好,叫醒吃货
大头和尚去等到
百媚和尚吃第30个馒头
牛鼻子和尚吃第29个馒头
花和尚和尚吃第28个馒头
花和尚和尚吃第27个馒头
百媚和尚吃第26个馒头
牛鼻子和尚吃第25个馒头
花和尚和尚吃第24个馒头
百媚和尚吃第23个馒头
牛鼻子和尚吃第22个馒头
花和尚和尚吃第21个馒头
百媚和尚吃第20个馒头
牛鼻子和尚吃第19个馒头
花和尚和尚吃第18个馒头
百媚和尚吃第17个馒头
牛鼻子和尚吃第16个馒头
花和尚和尚吃第15个馒头
百媚和尚吃第14个馒头
牛鼻子和尚吃第13个馒头
百媚和尚吃第12个馒头
花和尚和尚吃第11个馒头
牛鼻子和尚吃第10个馒头
花和尚和尚吃第9个馒头
百媚和尚吃第8个馒头
牛鼻子和尚吃第7个馒头
百媚和尚吃第6个馒头
牛鼻子和尚吃第5个馒头
花和尚和尚吃第4个馒头
百媚和尚吃第3个馒头
花和尚和尚吃第2个馒头
牛鼻子和尚吃第1个馒头
没馒头了
…省略

原创粉丝点击