一、概念梳理
1、线程
1.1、什么是线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个线程是一个execution context(执行上下文),即一个cpu执行时所需要的一串指令。
1.2)、线程的工作方式
假设你正在读一本书,没有读完,你想休息一下,但是你想在回来时恢复到当时读的具体进度。有一个方法就是记下页数、行数与字数这三个数值,这些数值就是execution context。如果你的室友在你休息的时候,使用相同的方法读这本书。你和她只需要这三个数字记下来就可以在交替的时间共同阅读这本书了。
线程的工作方式与此类似。CPU会给你一个在同一时间能够做多个运算的幻觉,实际上它在每个运算上只花了极少的时间,本质上CPU同一时刻只干了一件事。它能这样做就是因为它有每个运算的execution context。就像你能够和你朋友共享同一本书一样,多任务也能共享同一块CPU
线程的优缺点:
优点: 共享内存(一个进程内),i/o操作可实现并发执行
缺点: 抢占资源,切换上下文非常耗时
个数: 一般依情况而定
使用场所: i/o密集型
2 、进程
一个程序的执行实例就是一个进程。每一个进程提供执行程序所需的所有资源。(进程本质上是资源的集合)
一个进程有一个虚拟的地址空间、可执行的代码、操作系统的接口、安全的上下文(记录启动该进程的用户和权限等等)、唯一的进程ID、环境变量、优先级类、最小和最大的工作空间(内存空间),还要有至少一个线程。
每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程
与进程相关的资源:
- 内存页(同一个进程中的所有线程共享同一个内存空间)
- 文件描述符(e.g. open sockets)
- 安全凭证(e.g.启动该进程的用户ID)
进程的优缺点:
优点: 可同时利用多个cpu,进行多个操作
缺点: 重新开辟内存空间,非常耗费资源
个数: 一般和cpu颗数相同
使用场所: 一般是计算密集型
3、 进程与线程区别
1.同一个进程中的线程共享同一内存空间,但是进程之间是独立的。
2.同一个进程中的所有线程的数据是共享的(进程通讯),进程之间的数据是独立的。
3.对主线程的修改可能会影响其他线程的行为,但是父进程的修改(除了删除以外)不会影响其他子进程。
4.线程是一个上下文的执行指令,而进程则是与运算相关的一簇资源。
5.同一个进程的线程之间可以直接通信,但是进程之间的交流需要借助中间代理来实现。
6.创建新的线程很容易,但是创建新的进程需要对父进程做一次复制。
7.一个线程可以操作同一进程的其他线程,但是进程只能操作其子进程。
8.线程启动速度快,进程启动速度慢(但是两者运行速度没有可比性)。
4、进程、线程、协程对比
请仔细理解如下的通俗描述
有一个老板想要开个工厂进行生产某件商品
他需要花一些财力物力制作一条生产线,这个生产线上有很多的器件以及材料这些所有的 为了能够生产剪子而准备的资源称之为:进程
只有生产线是不能够进行生产的,所以老板的找个工人来进行生产,这个工人能够利用这些材料最终一步步的将剪子做出来,这个来做事情的工人称之为:线程
这个老板为了提高生产率,想到3种办法:
在这条生产线上多招些工人,一起来做剪子,这样效率是成倍増长,即单进程 多线程方式
老板发现这条生产线上的工人不是越多越好,因为一条生产线的资源以及材料毕竟有限,所以老板又花了些财力物力购置了另外一条生产线,然后再招些工人这样效率又再一步提高了,即多进程 多线程方式
老板发现,现在已经有了很多条生产线,并且每条生产线上已经有很多工人了(即程序是多进程的,每个进程中又有多个线程),为了再次提高效率,老板想了个损招,规定:如果某个员工在上班时临时没事或者再等待某些条件(比如等待另一个工人生产完谋道工序 之后他才能再次工作) ,那么这个员工就利用这个时间去做其它的事情,那么也就是说:如果一个线程等待某些条件,可以充分利用这个时间去做其它事情,其实这就是:协程方式
简单总结
进程是操作系统资源分配的最小单位
线程是CPU调度的单位
进程切换需要的资源最大,效率很低
线程切换需要的资源一般,效率一般(当然在不考虑GIL的情况下)
协程切换任务资源很小,效率高
多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发
二、 线程
线程分为:单线程和多线程
1、单线程
在好些年前的MS-DOS时代,操作系统处理问题都是单任务的,我想做听音乐和看电影两件事儿,那么一定要先排一下顺序。
(好吧!我们不纠结在DOS时代是否有听音乐和看影的应用。^_^)
from time import ctime,sleep
def music():
for i in range(2):
print("I was listening to music. %s"%ctime() )
sleep(1)
def move():
for i in range(2):
print("I was at the movies! %s" % ctime())
sleep(5)
if __name__ == "__main__":
music()
move()
print("all over %s" % ctime())
我们先听了一首音乐,通过for循环来控制音乐的播放了两次,每首音乐播放需要1秒钟,sleep()来控制音乐播放的时长。接着我们又看了一场电影,
每一场电影需要5秒钟,因为太好看了,所以我也通过for循环看两遍。在整个休闲娱乐活动结束后,我通过
print "all over %s" %ctime()
看了一下当前时间,差不多该睡觉了。
运行结果:
I was listening to music. Mon Oct 15 11:33:26 2018
I was listening to music. Mon Oct 15 11:33:27 2018
I was at the movies! Mon Oct 15 11:33:28 2018
I was at the movies! Mon Oct 15 11:33:33 2018
all over Mon Oct 15 11:33:38 2018
其实,music()和move()更应该被看作是音乐和视频播放器,至于要播放什么歌曲和视频应该由我们使用时决定。所以,我们对上面代码做了改造:
from time import ctime,sleep
def music(name):
for i in range(2):
print("I was listening to music. %s %s"%(name,ctime()) )
sleep(1)
def move(name):
for i in range(2):
print("I was at the movies! %s %s" %(name,ctime()))
sleep(5)
if __name__ == "__main__":
music("小邋遢")
move("无双")
print("all over %s" % ctime())
"""
I was listening to music. 小邋遢 Mon Oct 15 11:37:20 2018
I was listening to music. 小邋遢 Mon Oct 15 11:37:21 2018
I was at the movies! 无双 Mon Oct 15 11:37:22 2018
I was at the movies! 无双 Mon Oct 15 11:37:27 2018
all over Mon Oct 15 11:37:32 2018
"""
2、多线程
2.1 线程常用方法
方法 | 注释 |
---|---|
start() | 线程准备就绪,等待CPU调度 |
setName() | 为线程设置名称 |
getName() | 获取线程名称 |
setDaemon(True) | 设置为守护线程 |
join() | 逐个执行每个线程,执行完毕后继续往下执行 |
run() | 线程被cpu调度后自动执行线程对象的run方法,如果想自定义线程类,直接重写run方法就行了 |
2.1.1 Thread类
1.普通创建方式
import time,threading
def run(n):
print("task===>",n)
time.sleep(1)
print("4s \n")
time.sleep(1)
print("2s \n")
time.sleep(1)
print("0s \n")
t1 = threading.Thread(target=run,args=("t1",))
t2 = threading.Thread(target=run,args=("t2",))
t1.start()
t2.start()
"""
task===> t1
task===> t2
4s
4s
2s
2s
0s
0s
Process finished with exit code 0
"""
2.继承threading.Thread来自定义线程类
其本质是重构Thread类中的run方法
import threading,time
class MyThread(threading.Thread):
def __init__(self,n):
super(MyThread,self).__init__()
self.n = n
def run(self):
print("task===>", self.n)
time.sleep(1)
print("4s \n")
time.sleep(1)
print("2s \n")
time.sleep(1)
print("0s \n")
if __name__ =="__main__":
t1 = MyThread("t1")
t2 = MyThread("t2")
t1.start()
t2.start()
"""
C:\Users\Songyu.Ji\venv\多线程\Scripts\python.exe "D:/Pycharm Community/多线程/test.py"
task===> t1
task===> t2
4s
4s
2s
2s
0s
0s
Process finished with exit code 0
"""
2.1.2 计算子线程执行的时间
注:sleep的时候是不会占用cpu的,在sleep的时候操作系统会把线程暂时挂起
join():等此线程执行完后,再执行其他线程或主线程
threading.current_thread():输出当前线程
import time,threading
def run(n):
print("task===>", n,threading.current_thread()) #输出当前的线程
time.sleep(1)
print("4s \n")
time.sleep(1)
print("2s \n")
time.sleep(1)
print("0s \n")
start_time = time.time()
t_obj = [] #定义列表用于存放子线程实例
for i in range(3):
t = threading.Thread(target=run,args=("t-%s" % i,))
t.start()
t_obj.append(t)
for tmp in t_obj:
#等此线程执行完后,再执行其他线程或主线程
t.join()
print("cost:", time.time() - start_time) #主线程
print(threading.current_thread()) #输出当前线程
"""
C:\Users\Songyu.Ji\venv\多线程\Scripts\python.exe "D:/Pycharm Community/多线程/test.py"
task===> t-0 <Thread(Thread-1, started 10500)>
task===> t-1 <Thread(Thread-2, started 5352)>
task===>t-2 <Thread(Thread-3, started 11664)>
4s
4s
4s
2s
2s
2s
0s
0s
0s
cost: 3.003960609436035
<_MainThread(MainThread, started 340)>
Process finished with exit code 0
"""
2.1.3 统计当前活跃的线程数
由于主线程比子线程快很多,当主线程执行active_count()时,其他子线程都还没执行完毕,因此利用主线程统计的活跃的线程数num = sub_num(子线程数量)+1(主线程本身)
import threading
import time
def run(n):
print("task", n)
time.sleep(1) #此时子线程停1s
for i in range(3):
t = threading.Thread(target=run, args=("t-%s" % i,))
t.start()
time.sleep(0.5) #主线程停0.5秒
print(threading.active_count()) #输出当前活跃的线程数
"""
task t-0
task t-1
task t-2
4
"""
由于主线程比子线程慢很多,当主线程执行active_count()时,其他子线程都已经执行完毕,因此利用主线程统计的活跃的线程数num = 1(主线程本身)
import threading
import time
def run(n):
print("task", n)
time.sleep(0.5) #此时子线程停0.5s
for i in range(3):
t = threading.Thread(target=run, args=("t-%s" % i,))
t.start()
time.sleep(1) #主线程停1秒
print(threading.active_count()) #输出活跃的线程数
"""
task t-0
task t-1
task t-2
1
"""
此外我们还能发现在python内部默认会等待最后一个进程执行完后再执行exit(),或者说python内部在此时有一个隐藏的join()。
2.2 守护进程
我们看下面这个例子,这里使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,因此当主进程结束后,子线程也会随之结束。所以当主线程结束后,整个程序就退出了。
子线程守护主线程(主线程一旦退出,子线程也随之退出)
import threading
import time
def run(n):
print("task", n)
time.sleep(1) #此时子线程停1s
print('3')
time.sleep(1)
print('2')
time.sleep(1)
print('1')
for i in range(3):
t = threading.Thread(target=run, args=("t-%s" % i,))
t.setDaemon(True) #把子进程设置为守护线程,必须在start()之前设置
t.start()
time.sleep(0.5) #主线程停0.5秒
print(threading.active_count()) #输出活跃的线程数
"""
task t-0
task t-1
task t-2
4
Process finished with exit code 0
"""
2.3 GIL
在非python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中,无论有多少核,同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。
GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操作cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。
Python多线程的工作过程:
python在使用多线程的时候,调用的是c语言的原生线程。
- 拿到公共数据
- 申请gil
- python解释器调用os原生线程
- os操作cpu执行运算
- 当该线程执行时间到后,无论运算是否已经执行完,gil都被要求释放
- 进而由其他进程重复上面的过程
- 等其他进程执行完后,又会切换到之前的线程(从他记录的上下文继续执行)
整个过程是每个线程执行自己的运算,当执行时间到就进行切换(context switch)。
-
python针对不同类型的代码执行效率也是不同的:
1、CPU密集型代码(各种循环处理、计算等等),在这种情况下,由于计算工作多,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。
2、IO密集型代码(文件处理、网络爬虫等涉及文件读写的操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。 -
使用建议?
python下想要充分利用多核CPU,就用多进程。因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)。
-
GIL在python中的版本差异:
1、在python2.x里,GIL的释放逻辑是当前线程遇见
IO操作
或者ticks计数达到100
时进行释放。(ticks可以看作是python自身的一个计数器,专门做用于GIL,每次释放后归零,这个计数可以通过sys.setcheckinterval 来调整)。而每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。并且由于GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,python的多线程效率并不高。
2、在python3.x中,GIL不使用ticks计数,改为使用计时器(执行时间达到阈值后,当前线程释放GIL),这样对CPU密集型程序更加友好,但依然没有解决GIL导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意。
2.4 线程锁
由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,你可以定义多个锁, 像下面的代码, 当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理。
由于线程之间是进行随机调度,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们也称此为“线程不安全”。
#实测:在python2.7、mac os下,运行以下代码可能会产生脏数据。但是在python3中就不一定会出现下面的问题。
import threading
import time
def run(n):
global num
num += 1
num = 0
t_obj = []
for i in range(20000):
t = threading.Thread(target=run, args=("t-%s" % i,))
t.start()
t_obj.append(t)
for t in t_obj:
t.join()
print "num:", num
"""
产生脏数据后的运行结果:
num: 19999
"""
2.5 互斥锁(mutex)(也成同步锁)
为了方式上面情况的发生,就出现了互斥锁(Lock)
import threading,time
def run(n):
# 获取锁
lock.acquire()
global num
num += 1
# 释放锁
lock.release()
#实例化一个锁对象
lock = threading.Lock()
num =0
t_obj=[]
for i in range(20000):
t = threading.Thread(target=run,args=("%s-i" % i,))
t.start()
t_obj.append(t)
for t in t_obj:
t.join()
print("num====",num)
"""
num==== 20000
"""
在线程间共享多个资源的时候,如果两个线程分别占有一分部资源的时候并且同时等待对方的资源时候,就会造成死锁。因为系统判断这段资源都正在使用,所以这2个线程在无外力作用下一直等待下去。
下面是死锁的一个列子:
import threading
import time
class MyThread(threading.Thread):
def doA(self):
lockA .acquire()
print(self.name,"getlockA",time.ctime())
time.sleep(3)
lockB.acquire()
print(self.name, "getlockB", time.ctime())
lockB.release()
lockA.release()
def doB(self):
lockB .acquire()
print(self.name,"getlockB",time.ctime())
time.sleep(2)
lockA.acquire()
print(self.name, "getlockA", time.ctime())
lockA.release()
lockB.release()
def run(self):
self.doA()
self.doB()
if __name__ =="__main__":
lockA = threading.Lock()
lockB = threading.Lock()
L= []
for i in range(5):
t = MyThread()
t.start()
L.append(t)
for i in L:
i.join()
print("ending.........")
2.6 递归锁
RLcok类的用法和Lock类一模一样,但它支持嵌套,,在多个锁没有释放的时候一般会使用使用RLcok类。
import threading,time
gl_num = 0
#实例化一个锁对象
lock = threading.Lock()
def Func():
lock.acquire()
global gl_num
gl_num += 1
time.sleep(1)
print("gl_num ====",gl_num)
lock.release()
for i in range(10):
t = threading.Thread(target=Func())
t.start()
"""
gl_num ==== 1
gl_num ==== 2
gl_num ==== 3
gl_num ==== 4
gl_num ==== 5
gl_num ==== 6
gl_num ==== 7
gl_num ==== 8
gl_num ==== 9
gl_num ==== 10
Process finished with exit code 0
"""
解决死锁(递归锁来解决)
import threading
import time
class MyThread(threading.Thread):
def doA(self):
r_lock .acquire()
print(self.name,"getlockA",time.ctime())
time.sleep(2)
r_lock.acquire()
print(self.name, "getlockB", time.ctime())
time.sleep(1)
r_lock.release()
r_lock.release()
def doB(self):
r_lock .acquire()
print(self.name,"getlockB",time.ctime())
time.sleep(2)
r_lock.acquire()
print(self.name, "getlockA", time.ctime())
r_lock.release()
r_lock.release()
def run(self):
self.doA()
self.doB()
if __name__ =="__main__":
r_lock = threading.RLock()
L= []
for i in range(5):
t = MyThread()
t.start()
L.append(t)
for i in L:
i.join()
print("ending.........")
2.7 信号量(BoundedSemaphore类)
互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
信号量用来控制线程并发数,BoundedSemaphore或Semaphore管理一个内置的计数器,每当调用acquire()时减1,调用realsea时加1,计数器不能小于0,当计数器为0时,acquire()将阻塞线程☞至同步锁定的状态,直到其它线程调用realsea()(类似于停车位的概念)。
BoundedSemaphore与Semaphore的唯一区别在于前者将在调用realsea()时检查计数器是否能超过计数器的初始值,如果超过了将抛出一个异常。
import threading,time
class MyThread(threading.Thread):
def run(self):
if semaphore.acquire(): #枷锁
print(self.name)
time.sleep(5)
semaphore.release()
if __name__ =="__main__":
#同时开启5个线程
semaphore = threading.Semaphore(5)
thrs=[]
for i in range(100):
thrs.append(MyThread())
for t in thrs:
t.start()
import threading,time
gl_num = 0
semaphore = threading.BoundedSemaphore()
def run(n):
# 加锁
semaphore.acquire()
global gl_num
gl_num += 1
print(gl_num)
time.sleep(1)
#释放锁
semaphore.release()
for i in range(3):
t = threading.Thread(target=run,args=("t-%s" % i,))
t.start()
while threading.active_count() !=1:
pass
else:
print("----------all threads done ----------------")
"""
1
2
3
----------all threads done ----------------
Process finished with exit code 0
"""
2.8 事件(Event类)(同步条件)
python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下几个方法:
方法 | 注释 |
---|---|
clear | 将flag设置为“False” |
set | 将flag设置为“True” |
is_set | 判断是否设置了flag |
wait | 会一直监听flag,如果没有检测到flag就一直处于阻塞状态 |
事件处理的机制:全局定义了一个“Flag”,当flag值为“False”,那么event.wait()就会阻塞,当flag值为“True”,那么event.wait()便不再阻塞。
#老板与员工
import threading
import time
class Worker(threading.Thread):
def run(self):
event.wait() #一旦event被设定,等同于pass
print("Worker:这边过来看看")
time.sleep(1)
event.clear()
event.wait()
print("Woker,OhYear!")
class Boss(threading.Thread):
def run(self):
print("Boss:今晚大家都要加班到半夜")
print(event.isSet())
event.set()
time.sleep(5)
print("Boss:加班结束,大家可以下班了")
print(event.isSet())
event.set()
if __name__ == "__main__":
event = threading.Event()
threads=[]
for i in range(5):
threads.append(Worker())
threads.append(Boss())
for t in threads:
t.start()
for i in threads:
i.join()
print("ending.........")
"""
Boss:今晚大家都要加班到半夜
False
Worker:这边过来看看
Worker:这边过来看看Worker:这边过来看看Worker:这边过来看看Worker:这边过来看看
Boss:加班结束,大家可以下班了
False
Woker,OhYear!Woker,OhYear!Woker,OhYear!Woker,OhYear!Woker,OhYear!
ending.........
Process finished with exit code 0
"""
#利用Event类模拟红绿灯
import threading
import time
event = threading.Event()
def lighter():
count = 0
event.set() #初始值为绿灯
while True:
if 5 < count <=10 :
event.clear() # 红灯,清除标志位
print("\33[41;1mred light is on...\033[0m")
elif count > 10:
event.set() # 绿灯,设置标志位
count = 0
else:
print("\33[42;1mgreen light is on...\033[0m")
time.sleep(1)
count += 1
def car(name):
while True:
if event.is_set(): #判断是否设置了标志位
print("[%s] running..."%name)
time.sleep(1)
else:
print("[%s] sees red light,waiting..."%name)
event.wait()
print("[%s] green light is on,start going..."%name)
light = threading.Thread(target=lighter,)
light.start()
car = threading.Thread(target=car,args=("MINI",))
car.start()
2.9 条件(Condition类)
使得线程等待,只有满足某条件时,才释放n个线程
2.10 定时器(Timer类)
定时器,指定n秒后执行某操作
from threading import Timer
def hello():
print("hello, world")
t = Timer(1, hello)
t.start() # after 1 seconds, "hello, world" will be printed
2.11 多线程利器----队列(queue)
队列是一种数据结构,队列是安全
1)列表是不安全的数据结构
import threading,time
li = [1,2,3,4,5]
def pri():
while li:
a = li[-1]
print(a)
time.sleep(1)
try:
li.remove(a)
except Exception as e:
print("------>",a,e)
t1 = threading.Thread(target=pri,args=())
t2= threading.Thread(target=pri,args=())
t1.start()
t2.start()
"""
报错
5
5
4------>
5 list.remove(x): x not in list
4
3
------> 4 list.remove(x): x not in list
3
2
------> 3 list.remove(x): x not in list
2
1
------> 2 list.remove(x): x not in list
1
------> 1 list.remove(x): x not in list
Process finished with exit code 0
"""
Queue是python标准库中的线程安全的队列(FIFO)实现,提供了一个适用于多线程编程的先进先出的数据结构,即队列,用来在生产者和消费者线程之间的信息传递
python提供了两个模块来实现多线程thread 和threading ,thread 有一些缺点,在threading 得到了弥补,为了不浪费你和时间,所以我们直接学习threading 就可以了。
1. 阻塞模式
import queue,time
q = queue.Queue(10) #创建一个队列
for i in range(10):
q.put('A')
time.sleep(0.5)
这是一段极其简单的代码(另有两个线程也在操作队列q),我期望每隔0.5秒写一个'A'到队列中,但总是不能如愿:间隔时间有时会远远超过0.5秒。原来,Queue.put()默认有 block = True 和 timeou 两个参数。当 block = True 时,写入是阻塞式的,阻塞时间由 timeou 确定。当队列q被(其他线程)写满后,这段代码就会阻塞,直至其他线程取走数据。Queue.put()方法加上 block=False 的参数,即可解决这个隐蔽的问题。但要注意,非阻塞方式写队列,当队列满时会抛出 exception Queue.Full 的异常。
2. 无法捕获 exception Queue.Empty 的异常
import queue
while True:
try:
data = q.get()
except Queue.Empty:
break
我的本意是用队列为空时,退出循环,但实际运行起来,却陷入了死循环。这个问题和上面有点类似:Queue.get()默认的也是阻塞方式读取数据,队列为空时,不会抛出 except Queue.Empty ,而是进入阻塞直至超时。 加上block=False 的参数,问题迎刃而解。
3. Queue常用方法汇总
task_done()
意味着之前入队的一个任务已经完成。由队列的消费者线程调用。每一个get()调用得到一个任务,接下来的task_done()调用告诉队列该任务已经处理完毕。
如果当前一个join()正在阻塞,它将在队列中的所有任务都处理完时恢复执行(即每一个由put()调用入队的任务都有一个对应的task_done()调用)。
join()
阻塞调用线程,直到队列中的所有任务被处理掉。
只要有数据被加入队列,未完成的任务数就会增加。当消费者线程调用task_done()(意味着有消费者取得任务并完成任务),未完成的任务数就会减少。当未完成的任务数降到0,join()解除阻塞。
put(item[, block[, timeout]])
将item放入队列中。
- 如果可选的参数block为True且timeout为空对象(默认的情况,阻塞调用,无超时)。
- 如果timeout是个正整数,阻塞调用进程最多timeout秒,如果一直无空空间可用,抛出Full异常(带超时的阻塞调用)。
- 如果block为False,如果有空闲空间可用将数据放入队列,否则立即抛出Full异常
其非阻塞版本为put_nowait
等同于put(item, False)
get([block[, timeout]])
从队列中移除并返回一个数据。block跟timeout参数同put
方法
其非阻塞方法为`get_nowait()`相当与get(False)
empty()
如果队列为空,返回True,反之返回False
Queue.Queue(maxsize=0) FIFO, 如果maxsize小于1就表示队列长度无限
Queue.LifoQueue(maxsize=0) LIFO, 如果maxsize小于1就表示队列长度无限
Queue.qsize() 返回队列的大小
Queue.empty() 如果队列为空,返回True,反之False
Queue.full() 如果队列满了,返回True,反之False
Queue.get([block[, timeout]]) 读队列,timeout等待时间
Queue.put(item, [block[, timeout]]) 写队列,timeout等待时间
Queue.queue.clear() 清空队列
4.队列的模式
1)基本FIFO队列 (先进先出)
class Queue.Queue(maxsize=0)
FIFO即First in First Out,先进先出。Queue提供了一个基本的FIFO容器,使用方法很简单,maxsize是个整数,指明了队列中能存放的数据个数的上限。一旦达到上限,插入会导致阻塞,直到队列中的数据被消费掉。如果maxsize小于或者等于0,队列大小没有限制。
import queue # 线程队列
"""
队列解决线程安全的问问题
队列的模式
1.先进先出 (FIFO)
2.先进后出
3.按照优先级来走
默认不写按照先进先出
"""
q = queue.Queue()
q.put(12)
q.put("hello")
q.put({"name":"alex"})
while not q.empty():
data = q.get()
print(data)
print("---------")
"""
12
---------
hello
---------
{'name': 'alex'}
---------
Process finished with exit code 0
"""
2)LIFO队列(先进后出)
class Queue.LifoQueue(maxsize=0)
LIFO即Last in First Out,后进先出。与栈的类似,使用也很简单,maxsize用法同上
import queue # 线程队列
"""
队列解决线程安全的问问题
队列的模式
1.先进先出 (FIFO)
2.先进后出
3.按照优先级来走
默认不写按照先进先出
"""
q = queue.LifoQueue()
q.put(12)
q.put("hello")
q.put({"name":"alex"})
while not q.empty():
data = q.get()
print(data)
print("---------")
"""
{'name': 'alex'}
---------
hello
---------
12
---------
Process finished with exit code 0
"""
可以看到仅仅是将Queue.Quenu类
替换为Queue.LifiQueue类
3)
优先级队列
class Queue.PriorityQueue(maxsize=0)
构造一个优先队列。maxsize用法同上。
import queue # 线程队列
import threading
class Job(object):
def __init__(self, priority, description):
self.priority = priority
self.description = description
print 'Job:',description
return
def __cmp__(self, other):
return cmp(self.priority, other.priority)
q = Queue.PriorityQueue()
q.put(Job(3, 'level 3 job'))
q.put(Job(10, 'level 10 job'))
q.put(Job(1, 'level 1 job'))
def process_job(q):
while True:
next_job = q.get()
print 'for:', next_job.description
q.task_done()
workers = [threading.Thread(target=process_job, args=(q,)),
threading.Thread(target=process_job, args=(q,))
]
for w in workers:
w.setDaemon(True)
w.start()
q.join()
"""
Job: level 3 job
Job: level 10 job
Job: level 1 job
for: level 1 job
for: level 3 job
for: job: level 10 job
"""
生产者消费者模型:
为什么要使用生产者和消费者模式
在线程的世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发中,如果生产者处理速度很快而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者与消费者的模式。
什么是生产者消费者模式
生产者消费者模式是通过有个容量来解决生产者和消费者的解耦组合问题。生产者和消费者之间不能进行通讯,而通过阻塞队列来进行通讯。所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是从阻塞队列中取数据,阻塞队列就相当于一个缓冲区,平衡了消费者和生产者的处理能力。
就像在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去领饭菜也不需要找厨师,直接去前台领取即可。这也是一个解耦的过程。
import time,random
import queue,threading
q = queue.Queue()
def Producer(name):
count = 0
while count <10:
print("making........")
time.sleep(5)
q.put(count)
print('Producer %s has produced %s baozi..' %(name, count))
count +=1
#q.task_done()
q.join()
print("ok......")
def Consumer(name):
count = 0
while count <10:
time.sleep(random.randrange(4))
# if not q.empty():
# print("waiting.....")
#q.join()
data = q.get()
print("eating....")
time.sleep(4)
q.task_done()
#print(data)
print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))
# else:
# print("-----no baozi anymore----")
count +=1
p1 = threading.Thread(target=Producer, args=('A君',))
c1 = threading.Thread(target=Consumer, args=('B君',))
c2 = threading.Thread(target=Consumer, args=('C君',))
c3 = threading.Thread(target=Consumer, args=('D君',))
p1.start()
c1.start()
c2.start()
c3.start()
练习:
科技在发展,时代在进步,我们的CPU也越来越快,CPU抱怨,P大点事儿占了我一定的时间,其实我同时干多个活都没问题的;于是,操作系统就进入了多任务时代。我们听着音乐吃着火锅的不在是梦想。
继续对上面的例子进行改造,引入threadring来同时播放音乐和视频:
#首先导入threading 模块,这是使用多线程的前提。
import threading
from time import ctime,sleep
def music(func):
for i in range(2):
print("I was listening to %s. %s" %(func,ctime()))
sleep(4)
def move(func):
for i in range(2):
print("I was at the %s! %s" % (func, ctime()))
sleep(5)
"""
创建了threads数组,创建线程t1,使用threading.Thread()方法,
在这个方法中调用music方法target=music,args方法对music进行传参。
把创建好的线程t1装到threads数组中。
接着以同样的方式创建线程t2,并把t2也装到threads数组。
"""
threads = []
t1= threading.Thread(target=music,args=("小邋遢",))
threads.append(t1)
t2= threading.Thread(target=move,args=("无双",))
threads.append(t2)
if __name__ =="__main__":
"""
最后通过for循环遍历数组。(数组被装载了t1和t2两个线程)
"""
for t in threads:
"""
setDaemon(True)将线程声明为守护线程,
必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。
子线程启动后,父线程也继续执行下去,当父线程执行完最后一条语句print "all over %s" %ctime()后,没有等待子线程,直接就退出了,同时子线程也一同结束。
"""
t.setDaemon(True)
t.start() #开始线程活动。
"""
我们只对上面的程序加了个join()方法,用于等待线程终止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
注意: join()方法的位置是在for循环外的,也就是说必须等待for循环里的两个进程都结束后,才去执行主进程。
"""
t.join()
print("------all over %s-----" % ctime())
"""
I was listening to 小邋遢. Mon Oct 15 14:11:24 2018
I was at the 无双! Mon Oct 15 14:11:24 2018
I was listening to 小邋遢. Mon Oct 15 14:11:28 2018
I was at the 无双! Mon Oct 15 14:11:29 2018
------all over Mon Oct 15 14:11:34 2018-----
Process finished with exit code 0
"""