1 引言

  上一篇博文详细总结了Python进程的用法,这一篇博文来所以说Python中线程的用法。实际上,程序的运行都是以线程为基本单位的,每一个进程中都至少有一个线程(主线程),线程又可以创建子线程。线程间共享数据比进程要容易得多(轻而易举),进程间的切换也要比进程消耗CPU资源少。

  线程管理可以通过thead模块(Python中已弃用)和threading 模块,但目前主要以threading模块为主。因为更加先进,有更好的线程支持,且 threading模块的同步原语远多于thread模块。另外,thread 模块中的一些属性会和 threading 模块有冲突。故,本文创建线程和使用线程都通过threading模块进行。

  threading模块提供的类: Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local。

  threading 模块提供的常用方法:

  threading.currentThread(): 返回当前的线程变量。

  threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

  threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

  threading 模块提供的常量:

  threading.TIMEOUT_MAX 设置threading全局超时时间。

2 创建线程

  无论是用自定义函数的方法创建线程还是用自定义类的方法创建线程与创建进程的方法极其相似的,不过,创建线程时,可以不在“if __name__==”__main__:”语句下进行”。无论是哪种方式,都必须通过threading模块提供的Thread类进行。Thread类常用属性和方法如下。

  Thread类属性:

  name:线程名

  ident:线程的标识符

  daemon:布尔值,表示这个线程是否是守护线程

  Thread类方法:

  __init__(group=None,target=None,name=None,args=(),kwargs={},verbose=None,daemon=None):实例化一个线程对象,需要一个可调用的target对象,以及参数args或者kwargs。还可以传递name和group参数。daemon的值将会设定thread.daemon的属性

  start():开始执行该线程

  run():定义线程的方法。(通常开发者应该在子类中重写)

  join(timeout=None):直至启动的线程终止之前一直挂起;除非给出了timeout(单位秒),否则一直被阻塞

  isAlive:布尔值,表示这个线程是否还存活(驼峰式命名,python2.6版本开始已被取代)

  isDaemon():布尔值,表示是否是守护线程

  setDaemon(布尔值):在线程start()之前调用,把线程的守护标识设定为指定的布尔值

  在下面两小节我们分别通过代码来演示。

2.1 自定义函数的方式创建线程

import osimport timeimport threadingdef fun(n):print('子线程开始运行……')time.sleep(1)my_thread_name = threading.current_thread().name#获取当前线程名称my_thread_id = threading.current_thread().ident#获取当前线程idprint('当前线程为:{},线程id为:{},所在进程为:{},您输入的参数为:{}'.format(my_thread_name ,my_thread_id , os.getpid(),n))print('子线程运行结束……')t = threading.Thread(target=fun , name='线程1',args=('参数1',))t.start()time.sleep(2)main_thread_name = threading.current_thread().name#获取当前线程名称main_thread_id = threading.current_thread().ident#获取当前线程idprint('主线程为:{},线程id为:{},所在进程为:{}'.format(main_thread_name ,main_thread_id , os.getpid()))

2.2 类的方式创建线程

import osimport timeimport threadingclass MyThread(threading.Thread):def __init__(self , n , name=None):super().__init__()self.name = nameself.n = ndef run(self):print('子线程开始运行……')time.sleep(1)my_thread_name = threading.current_thread().name#获取当前线程名称my_thread_id = threading.current_thread().ident#获取当前线程idprint('当前线程为:{},线程id为:{},所在进程为:{},您输入的参数为:{}'.format(my_thread_name ,my_thread_id , os.getpid(),self.n))print('子线程运行结束……')t = MyThread(name='线程1', n=1)t.start()time.sleep(2)main_thread_name = threading.current_thread().name#获取当前线程名称main_thread_id = threading.current_thread().ident#获取当前线程idprint('主线程为:{},线程id为:{},所在进程为:{}'.format(main_thread_name ,main_thread_id , os.getpid()))

  输出结果:

  子线程开始运行……

  当前线程为:线程1,线程id为:11312,所在进程为:4532,您输入的参数为:1

  子线程运行结束……

  主线程为:MainThread,线程id为:10868,所在进程为:4532

  上述两块代码输出结果是一样的(id不一样),观察输出结果可以发现,子线程和主线程所在的进程都是一样的,证明是在同一进程中的进程。

3 Thread的常用方法和属性

3.1 守护线程:Deamon

  Thread类有一个名为deamon的属性,标志该线程是否为守护线程,默认值为False,当为设为True是表示设置为守护线程。是否是守护线程有什么区别呢?我们先来看看deamon值为False(默认)情况时:

import osimport timeimport threadingdef fun():print('子线程开始运行……')for i in range(6):#运行3秒,每秒输出一次time.sleep(1)my_thread_name = threading.current_thread().nameprint('{}已运行{}秒……'.format(my_thread_name ,i+1))print('子线程运行结束……')print('主线程开始运行……')t = threading.Thread(target=fun , name='线程1')print('daemon的值为:{}'.format(t.daemon))t.start()for i in range(3):time.sleep(1)my_thread_name = threading.current_thread().nameprint('{}已运行{}秒……'.format(my_thread_name, i+1))print('主线程结束运行……')

  输出结果:

  主线程开始运行……

  daemon的值为:False

  子线程开始运行……

  MainThread已运行1秒……

  线程1已运行1秒……

  MainThread已运行2秒……

  线程1已运行2秒……

  MainThread已运行3秒……

  主线程结束运行……

  线程1已运行3秒……

  线程1已运行4秒……

  线程1已运行5秒……

  线程1已运行6秒……

  子线程运行结束……

  代码中,主线程只需要运行3秒即可结束,但子线程需要运行6秒,从运行结果中可以看到,主线程代码运行结束后,子线程还可以继续运行,这就是非守护线程的特征。

再来看看daemon值为True时:

import timeimport threadingdef fun():print('子线程开始运行……')for i in range(6):#运行3秒,每秒输出一次time.sleep(1)my_thread_name = threading.current_thread().nameprint('{}已运行{}秒……'.format(my_thread_name ,i+1))print('子线程运行结束……')print('主线程开始运行……')t = threading.Thread(target=fun , name='线程1')t.daemon=True #设置为守护线程print('daemon的值为:{}'.format(t.daemon))t.start()for i in range(3):time.sleep(1)my_thread_name = threading.current_thread().nameprint('{}已运行{}秒……'.format(my_thread_name, i+1))print('主线程结束运行……')

  输出结果:

  主线程开始运行……

  daemon的值为:True

  子线程开始运行……

  MainThread已运行1秒……

  线程1已运行1秒……

  MainThread已运行2秒……

  线程1已运行2秒……

  MainThread已运行3秒……

  主线程结束运行……

  从运行结果中可以看出,当deamon值为True,即设为守护线程后,只要主线程结束了,无论子线程代码是否结束,都得跟着结束,这就是守护线程的特征。另外,修改deamon的值必须在线程start()方法调用之前,否则会报错。

3.2 join()方法

  join()方法的作用是在调用join()方法处,让所在线程(主线程)同步的等待被join的线程(下面的p线程),只有p线程结束。我们尝试在不同的位置调用join方法,对比运行结果。首先在p线程一开始的位置进行join:

import timeimport threadingdef fun():print('子线程开始运行……')for i in range(6):#运行3秒,每秒输出一次time.sleep(1)my_thread_name = threading.current_thread().nameprint('{}已运行{}秒……'.format(my_thread_name ,i+1))print('子线程运行结束……')print('主线程开始运行……')t = threading.Thread(target=fun , name='线程1')t.daemon=True #设置为守护线程t.start()t.join() #此处进行joinfor i in range(3):time.sleep(1)my_thread_name = threading.current_thread().nameprint('{}已运行{}秒……'.format(my_thread_name, i+1))print('主线程结束运行……')

  输出结果:

  主线程开始运行……

  子线程开始运行……

  线程1已运行1秒……

  线程1已运行2秒……

  线程1已运行3秒……

  线程1已运行4秒……

  线程1已运行5秒……

  线程1已运行6秒……

  子线程运行结束……

  MainThread已运行1秒……

  MainThread已运行2秒……

  MainThread已运行3秒……

  主线程结束运行……

  可以看出,等子线程运行完之后,主线程才继续join下面的代码。然后在主线程即将结束时进行join:

import timeimport threadingdef fun():print('子线程开始运行……')for i in range(6):#运行3秒,每秒输出一次time.sleep(1)my_thread_name = threading.current_thread().nameprint('{}已运行{}秒……'.format(my_thread_name ,i+1))print('子线程运行结束……')print('主线程开始运行……')t = threading.Thread(target=fun , name='线程1')t.daemon=True #设置为守护线程t.start()for i in range(3):time.sleep(1)my_thread_name = threading.current_thread().nameprint('{}已运行{}秒……'.format(my_thread_name, i+1))t.join()print('主线程结束运行……')

  输出结果:

  主线程开始运行……

  子线程开始运行……

  MainThread已运行1秒……

  线程1已运行1秒……

  MainThread已运行2秒……

  线程1已运行2秒……

  MainThread已运行3秒……

  线程1已运行3秒……

  线程1已运行4秒……

  线程1已运行5秒……

  线程1已运行6秒……

  子线程运行结束……

  主线程结束运行……

  上面代码中,子线程是设置为守护线程的,如果没有调用join()方法主线程3秒结束,子线程也会跟着结束,但是从运行结果中我们可以看出,主线程3秒后,陷入等待,等子线程运行完之后,才会继续下面的代码。

4 线程间的同步机制

  在默认情况在,多个线程之间是并发执行的,这就可能给数据代码不安全性,例如有一个全局变量num=10,线程1、线程2每次读取该变量后在原有值基础上减1。但,如果线程1读取num的值(num=10)后,还没来得及减1,CPU就切换去执行线程2,线程2也去读取num,这时候读取到的值也还是num=10,然后让num=9,这是CPU有切换回线程1,因为线程1读取到的值是原来的num=10,所以做减1运算后,也做出num=9的结果。两个线程都执行了该任务,但最后的值可不是8。如下代码所示:

import timeimport threadingdef fun():global numtemp = numtime.sleep(0.2)temp -= 1num = tempprint('主线程开始运行……')t_lst = []num =10 # 全局变量for i in range(10):t = threading.Thread(target=fun)t_lst.append(t)t.start()[t.join() for t in t_lst]print('num最后的值为:{}'.format(num))print('主线程结束运行……')

  输出结果:

  主线程开始运行……

  num最后的值为:9

  主线程结束运行……

  最后结果为9,不是0。这就造成了数据混乱。所以,就有了线程同步机制。

4.1 互斥锁:Lock

  线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源设置一个状态:锁定和非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

import timeimport threadingdef fun(lock):lock.acquire()global numtemp = numtime.sleep(0.2)temp -= 1num = templock.release()print('主线程开始运行……')t_lst = []num =10 # 全局变量lock = threading.Lock()for i in range(10):t = threading.Thread(target=fun , args=(lock,))t_lst.append(t)t.start()[t.join() for t in t_lst]print('num最后的值为:{}'.format(num))print('主线程结束运行……')

  输出结果:

  主线程开始运行……

  num最后的值为:0

  主线程结束运行……

  可以看到,最后输出结果为0,值正确。当然,如果你运行了上述两块代码,你就会发现,使用了锁之后,代码运行速度明显降低,这是因为线程由原来的并发执行变成了串行,不过数据安全性得到保证。

  使用Lock的时候必须注意是否会陷入死锁,所谓死锁是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。关于死锁一个著名的模型是“科学家吃面”模型:

import timefrom threading import Threadfrom threading import Lockdef eatNoodles_1(noodle_lock, fork_lock, scientist):noodle_lock.acquire()print('{} 拿到了面'.format(scientist))fork_lock.acquire()print('{} 拿到了叉子'.format(scientist))time.sleep(1)print('{} 吃到了面'.format(scientist))fork_lock.release()noodle_lock.release()print('{} 放下了面'.format(scientist))print('{} 放下了叉子'.format(scientist))def eatNoodles_2(noodle_lock, fork_lock, scientist):fork_lock.acquire()print('{} 拿到了叉子'.format(scientist))noodle_lock.acquire()print('{} 拿到了面'.format(scientist))print('{} 吃到了面'.format(scientist))noodle_lock.release()print('{} 放下了面'.format(scientist))fork_lock.release()print('{} 放下了叉子'.format(scientist))scientist_list1 = ['霍金','居里夫人']scientist_list2 = ['爱因斯坦','富兰克林']noodle_lock  = Lock()fork_lock = Lock()for i in scientist_list1:t = Thread(target=eatNoodles_1, args=(noodle_lock, fork_lock, i))t.start()for i in scientist_list2:t = Thread(target=eatNoodles_2, args=(noodle_lock, fork_lock, i))t.start()

  输出结果:

  霍金 拿到了面

  霍金 拿到了叉子

  霍金 吃到了面

  霍金 放下了面

  霍金 放下了叉子

  爱因斯坦 拿到了叉子

  居里夫人 拿到了面

  霍金吃完后,爱因斯坦拿到了叉子,把叉子锁住了;居里夫人拿到了面,把面锁住了。爱因斯坦就想:居里夫人不给我面,我就吃不了面,所以我不给叉子。居里夫人就想:爱因斯坦不给我叉子我也吃不了面,我就不给叉子。所以就陷入了死循环。

  为了解决Lock死锁的情况,就有了递归锁:RLock。

4.2 递归锁:RLock

  所谓的递归锁也被称为“锁中锁”,指一个线程可以多次申请同一把锁,但是不会造成死锁,这就可以用来解决上面的死锁问题。

import timefrom threading import Threadfrom threading import RLockdef eatNoodles_1(noodle_lock, fork_lock, scientist):noodle_lock.acquire()print('{} 拿到了面'.format(scientist))fork_lock.acquire()print('{} 拿到了叉子'.format(scientist))time.sleep(1)print('{} 吃到了面'.format(scientist))fork_lock.release()noodle_lock.release()print('{} 放下了面'.format(scientist))print('{} 放下了叉子'.format(scientist))def eatNoodles_2(noodle_lock, fork_lock, scientist):fork_lock.acquire()print('{} 拿到了叉子'.format(scientist))noodle_lock.acquire()print('{} 拿到了面'.format(scientist))print('{} 吃到了面'.format(scientist))noodle_lock.release()print('{} 放下了面'.format(scientist))fork_lock.release()print('{} 放下了叉子'.format(scientist))scientist_list1 = ['霍金','居里夫人']scientist_list2 = ['爱因斯坦','富兰克林']noodle_lock=fork_lock = RLock()for i in scientist_list1:t = Thread(target=eatNoodles_1, args=(noodle_lock, fork_lock, i))t.start()for i in scientist_list2:t = Thread(target=eatNoodles_2, args=(noodle_lock, fork_lock, i))t.start()

  上面代码可以正常运行到所有科学家吃完面条。

  RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次

4.3 Condition

  Condition可以认为是一把比Lock和RLOK更加高级的锁,其在内部维护一个琐对象(默认是RLock),可以在创建Condigtion对象的时候把琐对象作为参数传入。Condition也提供了acquire, release方法,其含义与琐的acquire, release方法一致,其实它只是简单的调用内部琐对象的对应的方法而已。Condition内部常用方法如下:

  1)acquire(): 上线程锁

  2)release(): 释放锁

  3)wait(timeout): 线程挂起,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。

  4)notify(n=1): 通知其他线程,那些挂起的线程接到这个通知之后会开始运行,默认是通知一个正等待该condition的线程,最多则唤醒n个等待的线程。notify()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。notify()不会主动释放Lock。

  5)notifyAll(): 如果wait状态线程比较多,notifyAll的作用就是通知所有线程

  需要注意的是,notify()方法、notifyAll()方法只有在占用琐(acquire)之后才能调用,否则将会产生RuntimeError异常。

  用Condition来实现生产者消费者模型:

import threadingimport time# 生产者def produce(con):# 锁定线程global numcon.acquire()print("工厂开始生产……")while True:num += 1print("已生产商品数量:{}".format(num))time.sleep(1)if num >= 5:print("商品数量达到5件,仓库饱满,停止生产……")con.notify()  # 唤醒消费者con.wait()# 生产者自身陷入沉睡# 释放锁con.release()# 消费者def consumer(con):con.acquire()global numprint("消费者开始消费……")while True:num -= 1print("剩余商品数量:{}".format(num))time.sleep(2)if num <= 0:print("库存为0,通知工厂开始生产……")con.notify()  # 唤醒生产者线程con.wait()    # 消费者自身陷入沉睡con.release()con = threading.Condition()num = 0p = threading.Thread(target=produce , args=(con ,))c = threading.Thread(target=consumer , args=(con ,))p.start()c.start()

  输出结果:

  工厂开始生产……

  已生产商品数量:1

  已生产商品数量:2

  已生产商品数量:3

  已生产商品数量:4

  已生产商品数量:5

  商品数量达到5件,仓库饱满,停止生产……

  消费者开始消费……

  剩余商品数量:4

  剩余商品数量:3

  剩余商品数量:2

  剩余商品数量:1

  剩余商品数量:0

  库存为0,通知工厂开始生产……

  已生产商品数量:1

  已生产商品数量:2

  已生产商品数量:3

  已生产商品数量:4

4.4 信号量:Semaphore

  锁同时只允许一个线程更改数据,而信号量是同时允许一定数量的进程更改数据 。继续用上篇博文中用的的吃饭例子,加入有一下应用场景:有10个人吃饭,但只有一张餐桌,只允许做3个人,没上桌的人不允许吃饭,已上桌吃完饭离座之后,下面的人才能抢占桌子继续吃饭,如果不用信号量,肯定是10人一窝蜂一起吃饭:

from threading import Threadimport timeimport randomdef fun(i):print('{}号顾客上座,开始吃饭'.format(i))time.sleep(random.random())print('{}号顾客吃完饭了,离座'.format(i))if __name__=='__main__':for i in range(20):p = Thread(target=fun, args=(i,))p.start()

  输出结果:

  0号顾客上座,开始吃饭

  1号顾客上座,开始吃饭

  2号顾客上座,开始吃饭

  3号顾客上座,开始吃饭

  4号顾客上座,开始吃饭

  5号顾客上座,开始吃饭

  6号顾客上座,开始吃饭

  7号顾客上座,开始吃饭

  8号顾客上座,开始吃饭

  9号顾客上座,开始吃饭

  3号顾客吃完饭了,离座

  4号顾客吃完饭了,离座

  2号顾客吃完饭了,离座

  0号顾客吃完饭了,离座

  8号顾客吃完饭了,离座

  5号顾客吃完饭了,离座

  1号顾客吃完饭了,离座

  6号顾客吃完饭了,离座

  9号顾客吃完饭了,离座

  7号顾客吃完饭了,离座

  使用信号量之后:

from threading import Threadimport timeimport randomfrom threading import Semaphoredef fun(i , sem):sem.acquire()print('{}号顾客上座,开始吃饭'.format(i))time.sleep(random.random())print('{}号顾客吃完饭了,离座'.format(i))sem.release()if __name__=='__main__':sem = Semaphore(3)for i in range(10):p = Thread(target=fun, args=(i,sem))p.start()

  输出结果:

  0号顾客上座,开始吃饭

  1号顾客上座,开始吃饭

  2号顾客上座,开始吃饭

  2号顾客吃完饭了,离座

  3号顾客上座,开始吃饭

  0号顾客吃完饭了,离座

  4号顾客上座,开始吃饭

  1号顾客吃完饭了,离座

  5号顾客上座,开始吃饭

  3号顾客吃完饭了,离座

  6号顾客上座,开始吃饭

  5号顾客吃完饭了,离座

  7号顾客上座,开始吃饭

  4号顾客吃完饭了,离座

  8号顾客上座,开始吃饭

  8号顾客吃完饭了,离座

  9号顾客上座,开始吃饭

  9号顾客吃完饭了,离座

  6号顾客吃完饭了,离座

  7号顾客吃完饭了,离座

4.5 事件:Event

  如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时候就可以用threading为我们提供的Event对象,Event对象主要有一下几个方法:

  isSet():返回event的状态值;

  wait():如果 isSet()==False将阻塞线程;

  set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

  clear():恢复event的状态值为False。

  有如下需求:获取当前时间的秒数的个位数,如果小于5,设置子线程阻塞,如果大于5则设置子进程非阻塞。代码如下:

from threading import Event, Threadimport timefrom datetime import datetimedef func(e):print('子线程:开始运行……')while True:print('子线程:现在事件秒数是{}'.format(datetime.now().second))e.wait()  # 阻塞等待信号  这里插入了一个Flag  默认为 Falsetime.sleep(1)e = Event()p = Thread(target=func, args=(e,))p.daemon=Truep.start()for i in range(10):s = int(str(datetime.now().second)[-1])#获取当前秒数的个位数if s < 5:print('子线程线入阻塞状态')e.clear()  # 使插入的flag为False 线程线入阻塞状态else:print('子线程取消阻塞状态')e.set()  # 线程线入非阻塞状态time.sleep(1)e.set()print("主线程运行结束……")

  输出结果:

  子线程:开始运行……

  子线程:现在事件秒数是43

  子线程线入阻塞状态

  子线程线入阻塞状态

  子线程取消阻塞状态

  子线程取消阻塞状态

  子线程:现在事件秒数是46

  子线程取消阻塞状态

  子线程:现在事件秒数是47

  子线程取消阻塞状态

  子线程:现在事件秒数是48

  子线程取消阻塞状态

  子线程:现在事件秒数是49

  子线程线入阻塞状态

  子线程:现在事件秒数是50

  子线程线入阻塞状态

  子线程线入阻塞状态

  主线程运行结束……

4.6 定时器:Timer

  如果想要实现每隔一段时间就调用一个函数的话,就要在Timer调用的函数中,再次设置Timer。Timer是Thread类的一个子类。

  如果是多长时间后只执行一次:

import threadingimport timedef sayTime(name):print('你好,{}为您报时,现在时间是:{}'.format(name , time.ctime()))if __name__ == "__main__":timer = threading.Timer(2.0, sayTime, ["Jane"])timer.start()输出结果:你好,Jane为您报时,现在时间是:Thu Dec  6 15:03:41 2018如果要每个多长时间执行一次:import threadingimport timedef sayTime(name):print('你好,{}为您报时,现在时间是:{}'.format(name , time.ctime()))global timertimer = threading.Timer(3.0, sayTime, [name])timer.start()if __name__ == "__main__":timer = threading.Timer(2.0, sayTime, ["Jane"])timer.start()

  输出结果:

  你好,Jane为您报时,现在时间是:Thu Dec  6 15:04:30 2018

  你好,Jane为您报时,现在时间是:Thu Dec  6 15:04:33 2018

  你好,Jane为您报时,现在时间是:Thu Dec  6 15:04:36 2018

  你好,Jane为您报时,现在时间是:Thu Dec  6 15:04:39 2018

  ……

5 进程间的通行

5.1队列:Queue

  python中Queue模块提供了队列都实现了锁原语,是线程安全的,能够在多线程中直接使用。Queue中的队列包括以下三种:

  1)FIFO(先进先出)队列, 第一加入队列的任务, 被第一个取出;

  2)LIFO(后进先出)队列,最后加入队列的任务, 被第一个取出;

  3)PriorityQueue(优先级)队列, 保持队列数据有序, 最小值被先取出。

  Queue模块中的常用方法如下:

  qsize() 返回队列的规模

  empty() 如果队列为空,返回True,否则False

  full() 如果队列满了,返回True,否则False

  get([block[, timeout]])获取队列,timeout等待时间

  get_nowait() 相当get(False)

  put(item) 写入队列,timeout等待时间,如果队列已满再调用该方法会阻塞线程

  put_nowait(item) 相当put(item, False)

  task_done() 在完成一项工作之后,task_done()函数向任务已经完成的队列发送一个信号

  join() 实际上意味着等到队列为空,再执行别的操作。

import queueimport threadingdef fun():while True:try:data = q.get(block = True, timeout = 1) #不设置阻塞的话会一直去尝试获取资源except queue.Empty:print(' {}结束……'.format(threading.current_thread().name))breakprint(' {}取得数据:{}'.format(threading.current_thread().name , data))q.task_done()print(' {}结束……'.format(threading.current_thread().name))print("主线程开始运行……")q = queue.Queue(5)#往队列里面放5个数for i in range(5):q.put(i)for i in range(0, 3):t = threading.Thread(target=fun , name='线程'+str(i))t.start()q.join() #等待所有的队列资源都用完print("主线程结束运行……")

  输出结果:

  主线程开始运行……

  线程0取得数据:0

  线程0结束……

  线程0取得数据:1

  线程0结束……

  线程1取得数据:2

  线程0取得数据:3

  线程0结束……

  线程0取得数据:4

  线程0结束……

  线程1结束……

  主线程结束运行……

  线程1结束……

  线程2结束……

  线程0结束……

6 线程池

  在我们上面执行多个任务时,使用的线程方案都是“即时创建, 即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。一个线程的运行时间可以分为3部分:线程的启动时间、线程体的运行时间和线程的销毁时间。在多线程处理的情景中,如果线程不能被重用,就意味着每次创建都需要经过启动、销毁和运行3个过程。这必然会增加系统相应的时间,降低了效率。所以就有了线程池的诞生,

  由于线程预先被创建并放入线程池中,同时处理完当前任务之后并不销毁而是被安排处理下一个任务,因此能够避免多次创建线程,从而节省线程创建和销毁的开销,能带来更好的性能和系统稳定性。

from concurrent.futures import ThreadPoolExecutorimport timedef func(n):time.sleep(2)print(n)return n*nt = ThreadPoolExecutor(max_workers=5) # 最好不要超过CPU核数的5倍t_lst = []for i in range(20):r = t.submit(func , 1)#执行任务,传递参数t_lst.append(r.result())#获取任务返回结果t.shutdown()#相当于close() + join()print(t_lst)print('主线程运行结束……')

7 总结

  关于Python并发编程中的多线程部分就介绍完了,其中诸多描述略带仓储,后续博文中再来补充。

参考资料:

  https://www.cnblogs.com/linshuhui/p/9704128.html

https://www.cnblogs.com/yoyoketang/p/8337118.html

  https://www.cnblogs.com/chengd/articles/7770898.html

Python并发编程系列之多线程相关推荐

  1. python并发编程之semaphore(信号量)_Python 并发编程系列之多线程

    Python 并发编程系列之多线程 2 创建线程 2.1 函数的方式创建线程 2.2 类的方式创建线程 3 Thread 类的常用属性和方法 3.1 守护线程: Deamon 3.2 join()方法 ...

  2. Python|并发编程|爬虫|单线程|多线程|异步I/O|360图片|Selenium及JavaScript|Scrapy框架|BOM 和 DOM 操作简介|语言基础50课:学习(12)

    文章目录 系列目录 原项目地址 第37课:并发编程在爬虫中的应用 单线程版本 多线程版本 异步I/O版本 总结 第38课:抓取网页动态内容 Selenium 介绍 使用Selenium 加载页面 查找 ...

  3. 并发编程系列之五多线程synchronized是可重复加锁,重入锁

    并发编程系列之五多线程synchronized是可重复加锁,重入锁.对于重入锁的概念就是可以重复的加锁.. 示例1,在同一个类里面进行加锁,不同的方法调用,都一层一层的嵌套进行加锁,示例1演示重入锁的 ...

  4. Python并发编程系列之多进程(multiprocessing)

    1 引言 本篇博文主要对Python中并发编程中的多进程相关内容展开详细介绍,Python进程主要在multiprocessing模块中,本博文以multiprocessing种Process类为中心 ...

  5. Python 并发编程之使用多线程和多处理器

    在Python编码中我们经常讨论的一个方面就是如何优化模拟执行的性能.尽管在考虑量化代码时NumPy.SciPy和pandas在这方面已然非常有用,但在构建事件驱动系统时我们无法有效地使用这些工具.有 ...

  6. Java并发编程系列18:多线程之生产者和消费者模式_信号灯法(wait/notify通知机制)

    1.生产者消费者模式 生产者消费者问题(Producer-consumer problem),也称为有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例.该问题 ...

  7. Java 并发编程系列之带你了解多线程

    早期的计算机不包含操作系统,它们从头到尾执行一个程序,这个程序可以访问计算机中的所有资源.在这种情况下,每次都只能运行一个程序,对于昂贵的计算机资源来说是一种严重的浪费. 操作系统出现后,计算机可以运 ...

  8. python 并发编程 多线程 目录

    线程理论 python 并发编程 多线程 开启线程的两种方式 python 并发编程 多线程与多进程的区别 python 并发编程 多线程 Thread对象的其他属性或方法 python 并发编程 多 ...

  9. python并发编程:协程asyncio、多线程threading、多进程multiprocessing

    python并发编程:协程.多线程.多进程 CPU密集型计算与IO密集型计算 多线程.多进程与协程的对比 多线程 创建多线程的方法 多线程实现的生产者-消费者爬虫 Lock解决线程安全问题 使用线程池 ...

最新文章

  1. 第5天:基于类的视图与中间件
  2. [ROBOT] python library 如何能获取到ROBOT框架里面的全局变量,例如${OUTPUT DIR}等
  3. CRK计算机,crk_world
  4. CodeForces - 1305C Kuroni and Impossible Calculation(鸽巢原理)
  5. java_math_BigDecimal
  6. 五阿哥钢铁电商资深运维工程师手把手教你这样玩企业组网
  7. oracle树子类遍历父类_不懂数据库索引的底层原理?那是因为你心里没点b树
  8. 使用内存映射文件来共享数据
  9. [xdoj1227]Godv的数列(crt+lucas)
  10. JS编程建议——72:惰性载入函数
  11. oppo手机硬件测试软件,OPPO怎么测试手机硬件,只需要做这一步,进入工程模式...
  12. 金士顿 DT101 G2 8GU盘量产全过程图解(群联篇)(2)
  13. ctfshow菜狗杯wp
  14. wireshark抓包:分析阿里小蜜网络通信方式
  15. 小型机与PC服务器的对比区别
  16. [VCS]Coverage Options Introduction
  17. 《计算机网络管理》_Chap2
  18. 基于粒子群算法的冷热电三联供综合能源系统优化调度
  19. Springboot 结合 Netty 实战聊天系统
  20. 【java毕业设计】基于javaEE+SSH+MySql+MVC的动漫论坛设计与实现(毕业论文+程序源码)——动漫论坛

热门文章

  1. 小福利,带你快速入门sumifs多条件求和函数、设置下拉菜单结合vlookup函数双条件查找数据、excel的切片器(表关联)、数据透视表、数据透视图
  2. STM32F103ZET6开发板实现跑马灯实验
  3. win10通过网线连接树莓派
  4. 【adb】cmd命令行输入adb时始终提示adb为非内部命令
  5. 网络版五子棋程序的开发
  6. python使用matplotlib库构建动态图表 --基于animation模块
  7. 《为你打开一扇门》| 赵丽宏
  8. ubuntu解压各种文件
  9. 14个python就业前景_python普通人学有什么用 就业前景和工资待遇怎么样
  10. Wireshark | 猿如意