本节内容
一、进程与线程的概念
1.1进程
1.2线程
1.3进程与线程的区别
二、线程
2、1启一个线程
2.2线程的2种调用方式
2.3 join
2.4 守护线程Daemon
2.5线程锁
2.6全局解析锁
2.7递归锁
2.8Semaphore(信号量)
2.9事件Events(红绿灯举例)
2.10queue队列
2.11生产消费者模型
三、进程
3.1多进程
3.2进程间通讯
3.3进程池
一、进程与线程的概念
1.1进程
什么是进程(process)?
以QQ为例, QQ 要以一个整体的形式暴露给操作系统管理,里面包含对各种资源的调用,内存的管理,网络接口的调用等。。。。对各种资源管理的集合 就可以称为: 进程。
程序的执行实例称为进程。每个进程都提供执行程序所需的资源。 进程具有虚拟地址空间,可执行代码,系统对象的打开句柄,安全上下文,唯一进程标识符,环境变量,优先级类,最小和最大工作集大小以及至少一个执行线程。 每个进程都使用单个线程启动,通常称为主线程,但可以从其任何线程创建其他线程。
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
有了进程为什么还要线程?
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:
-
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
-
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息、同时还能把别人发的消息显示在屏幕上呢?你会说,操作系统不是有分时么?但我的亲,分时是指在不同进程间的分时呀, 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀。
再直白一点, 一个操作系统就像是一个工厂,工厂里面有很多个生产车间,不同的车间生产不同的产品,每个车间就相当于一个进程,且你的工厂又穷,供电不足,同一时间只能给一个车间供电,为了能让所有车间都能同时生产,你的工厂的电工只能给不同的车间分时供电,但是轮到你的qq车间时,发现只有一个干活的工人,结果生产效率极低,为了解决这个问题,应该怎么办呢?。。。。没错,你肯定想到了,就是多加几个工人,让几个人工人并行工作,这每个工人,就是线程!
1.2线程
线程: 是操作系统最小的调度单位, 是一串指令的集合。是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。官网链接:https://docs.python.org/3/library/threading.html?highlight=threading#
线程是执行上下文,它是CPU执行指令流所需的所有信息。
假设你正在读一本书,而你现在想休息一下,但是你希望能够从你停下来的确切位置回来并继续阅读。实现这一目标的一种方法是记下页码,行号和字号。因此,阅读书籍的执行环境就是这三个数字。如果你有一个室友,并且她使用相同的技术,她可以在你不使用时拿走这本书,并从她停下的地方继续阅读。然后你可以把它拿回来,并从你原来的地方恢复。
线程以相同的方式工作。 CPU正在给你一种错觉,即它同时进行多次计算。它通过在每次计算上花费一点时间来做到这一点。它可以这样做,因为它具有每个计算的执行上下文。就像您可以与朋友共享一本书一样,许多任务可以共享CPU。在更技术层面上,执行上下文(因此是一个线程)由CPU寄存器的值组成。最后:线程与进程不同。线程是执行的上下文,而进程是与计算相关联的一堆资源。一个进程可以有一个或多个线程。澄清:与进程相关联的资源包括内存页面(进程中的所有线程具有相同的内存视图),文件描述符(例如,打开套接字)和安全凭证(例如,启动该进程的用户的ID)处理)。
1.3进程与线程的区别:
进程 要操作cpu , 必须要先创建一个线程 ,所有在同一个进程里的线程是共享同一块内存空间的。
1、线程共享创建它的进程的地址空间; 进程有自己的地址空间。(线程共享内存空间,进程的内存是独立的)
2、线程可以直接访问其进程的数据段; 进程拥有自己父进程数据段的副本。
3、线程可以直接与其进程的其他线程通信; 进程必须使用进程间通信来与兄弟进程通信。(同一个进程的线程之间可以直接交流,两个进程想通信,必须通过一个中间代理来实现)
4、创建新线程很简单, 创建新进程需要对其父进程进行一次克隆。
5、一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程。
6、对主线程的更改(取消,优先级更改等)可能会影响进程的其他线程的行为; 对父进程的更改不会影响子进程。
二、线程
2.1启动一个线程
举例子,演示一个最简单的多线程
1 import threading 2 import time 3 4 def run(n): 5 print("task",n) 6 time.sleep(2) 7 8 t1 = threading.Thread(target=run,args=("t1",)) 9 t2 = threading.Thread(target=run,args=("t2",)) 10 #总共运行了2秒,因为是并行的2秒 11 t1.start() 12 t2.start() 13 14 #比较,这里会运行4秒,是先后各两秒 15 # run(t1) 16 # run(t2)
2、2线程的2种调用方式
Python threading模块线程有2种调用方式,如下:
1 import threading 2 import time 3 4 5 def sayhi(num): # 定义每个线程要运行的函数 6 7 print("running on number:%s" % num) 8 9 time.sleep(3) 10 11 12 if __name__ == '__main__': 13 t1 = threading.Thread(target=sayhi, args=(1,)) # 生成一个线程实例 14 t2 = threading.Thread(target=sayhi, args=(2,)) # 生成另一个线程实例 15 16 t1.start() # 启动线程 17 t2.start() # 启动另一个线程 18 19 print(t1.getName()) # 获取线程名 20 print(t2.getName())
1 import threading 2 import time 3 4 5 class MyThread(threading.Thread): 6 def __init__(self, num): 7 threading.Thread.__init__(self) 8 self.num = num 9 10 def run(self): # 定义每个线程要运行的函数 11 12 print("running on number:%s" % self.num) 13 14 time.sleep(3) 15 16 17 if __name__ == '__main__': 18 t1 = MyThread(1) 19 t2 = MyThread(2) 20 t1.start() 21 t2.start()
2.3 join
1 import threading 2 import time 3 4 class MyThread(threading.Thread): 5 def __init__(self,n,sleep_time): 6 super(MyThread,self).__init__() 7 self.n = n 8 self.sleep_time = sleep_time 9 def run(self): 10 print("runnint task ",self.n ) 11 time.sleep(self.sleep_time) 12 print("task done,",self.n ) 13 14 15 t1 = MyThread("t1",2) 16 t2 = MyThread("t2",4) 17 18 t1.start() 19 t2.start() 20 21 t1.join() #=wait() 22 t2.join() 23 24 print("main thread....")
1 import threading 2 import time 3 4 def run(n): 5 print("task ",n ) 6 time.sleep(2) 7 print("task done",n) 8 9 start_time = time.time() 10 t_objs = [] #存线程实例 11 for i in range(50): 12 t = threading.Thread(target=run,args=("t-%s" %i ,)) 13 t.start() 14 t_objs.append(t) #为了不阻塞后面线程的启动,不在这里join,先放到一个列表里 15 16 for t in t_objs: #循环线程实例列表,等待所有线程执行完毕 17 t.join() 18 19 20 print("----------all threads has finished...") 21 print("cost:",time.time() - start_time) 22 # run("t1") 23 # run("t2")
在一个进程下开启多个线程与在一个进程下开启多个子进程的区别
1 from threading import Thread 2 from multiprocessing import Process 3 import os 4 5 def work(): 6 print('hello') 7 8 if __name__ == '__main__': 9 #在主进程下开启线程 10 t=Thread(target=work) 11 t.start() 12 print('主线程/主进程') 13 ''' 14 打印结果: 15 hello 16 主线程/主进程 17 ''' 18 19 #在主进程下开启子进程 20 t=Process(target=work) 21 t.start() 22 print('主线程/主进程') 23 ''' 24 打印结果: 25 主线程/主进程 26 hello 27 '''
1 from threading import Thread 2 from multiprocessing import Process 3 import os 4 5 def work(): 6 print('hello',os.getpid()) 7 8 if __name__ == '__main__': 9 #part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样 10 t1=Thread(target=work) 11 t2=Thread(target=work) 12 t1.start() 13 t2.start() 14 print('主线程/主进程pid',os.getpid()) 15 16 #part2:开多个进程,每个进程都有不同的pid 17 p1=Process(target=work) 18 p2=Process(target=work) 19 p1.start() 20 p2.start() 21 print('主线程/主进程pid',os.getpid())
1 from threading import Thread 2 from multiprocessing import Process 3 import os 4 def work(): 5 global n 6 n=0 7 8 if __name__ == '__main__': 9 # n=100 10 # p=Process(target=work) 11 # p.start() 12 # p.join() 13 # print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100 14 15 16 n=1 17 t=Thread(target=work) 18 t.start() 19 t.join() 20 print('主',n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据
练习
练习一:
1 #_*_coding:utf-8_*_ 2 #!/usr/bin/env python 3 import multiprocessing 4 import threading 5 6 import socket 7 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 8 s.bind(('127.0.0.1',8080)) 9 s.listen(5) 10 11 def action(conn): 12 while True: 13 data=conn.recv(1024) 14 print(data) 15 conn.send(data.upper()) 16 17 if __name__ == '__main__': 18 19 while True: 20 conn,addr=s.accept() 21 22 23 p=threading.Thread(target=action,args=(conn,)) 24 p.start()
1 #_*_coding:utf-8_*_ 2 #!/usr/bin/env python 3 4 5 import socket 6 7 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 8 s.connect(('127.0.0.1',8080)) 9 10 while True: 11 msg=input('>>: ').strip() 12 if not msg:continue 13 14 s.send(msg.encode('utf-8')) 15 data=s.recv(1024) 16 print(data)
练习二:三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件
1 from threading import Thread 2 msg_l=[] 3 format_l=[] 4 def talk(): 5 while True: 6 msg=input('>>: ').strip() 7 if not msg:continue 8 msg_l.append(msg) 9 10 def format_msg(): 11 while True: 12 if msg_l: 13 res=msg_l.pop() 14 format_l.append(res.upper()) 15 16 def save(): 17 while True: 18 if format_l: 19 with open('db.txt','a',encoding='utf-8') as f: 20 res=format_l.pop() 21 f.write('%s ' %res) 22 23 if __name__ == '__main__': 24 t1=Thread(target=talk) 25 t2=Thread(target=format_msg) 26 t3=Thread(target=save) 27 t1.start() 28 t2.start() 29 t3.start()
线程相关的其他方法
1 Thread实例对象的方法 2 # isAlive(): 返回线程是否活动的。 3 # getName(): 返回线程名。 4 # setName(): 设置线程名。 5 6 threading模块提供的一些方法: 7 # threading.currentThread(): 返回当前的线程变量。 8 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 9 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
1 from threading import Thread 2 import threading 3 from multiprocessing import Process 4 import os 5 6 def work(): 7 import time 8 time.sleep(3) 9 print(threading.current_thread().getName()) 10 11 12 if __name__ == '__main__': 13 #在主进程下开启线程 14 t=Thread(target=work) 15 t.start() 16 17 print(threading.current_thread().getName()) 18 print(threading.current_thread()) #主线程 19 print(threading.enumerate()) #连同主线程在内有两个运行的线程 20 print(threading.active_count()) 21 print('主线程/主进程') 22 23 ''' 24 打印结果: 25 MainThread 26 <_MainThread(MainThread, started 140735268892672)> 27 [<_MainThread(MainThread, started 140735268892672)>, <Thread(Thread-1, started 123145307557888)>] 28 主线程/主进程 29 Thread-1 30 '''
主线程等待子线程结束
1 from threading import Thread 2 import time 3 def sayhi(name): 4 time.sleep(2) 5 print('%s say hello' %name) 6 7 if __name__ == '__main__': 8 t=Thread(target=sayhi,args=('egon',)) 9 t.start() 10 t.join() 11 print('主线程') 12 print(t.is_alive()) 13 ''' 14 egon say hello 15 主线程 16 False 17 '''
2.4 守护线程Daemon
有些线程执行后台任务,例如发送keepalive数据包,或执行定期垃圾收集等等。 这些仅在主程序运行时才有用,并且一旦其他非守护程序线程退出就可以将它们终止。
如果没有守护程序线程,您必须跟踪它们,并在程序完全退出之前告诉它们退出。 通过将它们设置为守护程序线程,您可以让它们运行并忘记它们,当程序退出时,任何守护程序线程都会自动终止。
1 import threading 2 import time 3 4 def run(n): 5 print("task ",n ) 6 time.sleep(2) 7 print("task done",n,threading.current_thread()) 8 9 start_time = time.time() 10 t_objs = [] #存线程实例 11 for i in range(50): 12 t = threading.Thread(target=run,args=("t-%s" %i ,)) 13 t.setDaemon(True) #把当前线程设置为守护线程 14 t.start() 15 t_objs.append(t) #为了不阻塞后面线程的启动,不在这里join,先放到一个列表里 16 17 # for t in t_objs: #循环线程实例列表,等待所有线程执行完毕 18 # t.join() 19 20 time.sleep(2) 21 print("----------all threads has finished...",threading.current_thread(),threading.active_count()) 22 print("cost:",time.time() - start_time) 23 # run("t1") 24 # run("t2")
1 import time 2 import threading 3 4 5 def run(n): 6 print('[%s]------running---- ' % n) 7 time.sleep(2) 8 print('--done--') 9 10 11 def main(): 12 for i in range(5): 13 t = threading.Thread(target=run, args=[i, ]) 14 t.start() 15 t.join(1) 16 print('starting thread', t.getName()) 17 18 19 m = threading.Thread(target=main, args=[]) 20 m.setDaemon(True) # 将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务 21 m.start() 22 m.join(timeout=2) 23 print("---main thread done----")
注意:守护程序线程在关闭时突然停止。 他们的资源(例如打开文件,数据库事务等)可能无法正确发布。 如果您希望线程正常停止,请将它们设置为非守护进程并使用合适的信号机制(如Event)。
1 无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁 2 需要强调的是:运行完毕并非终止运行 3 #1.对主进程来说,运行完毕指的是主进程代码运行完毕 4 #2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕 5 6 详细解释: 7 8 #1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会 9 # 一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束, 10 11 #2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。 12 # 因为主线程的结束意味着进程的结束,进程整体的资源都将被回收, 13 # 而进程必须保证非守护线程都运行完毕后才能结束。
1 from threading import Thread 2 import time 3 def sayhi(name): 4 time.sleep(2) 5 print('%s say hello' %name) 6 7 if __name__ == '__main__': 8 t=Thread(target=sayhi,args=('egon',)) 9 t.setDaemon(True) #必须在t.start()之前设置 10 t.start() 11 12 print('主线程') 13 print(t.is_alive()) 14 ''' 15 主线程 16 True 17 '''
1 from threading import Thread 2 import time 3 def foo(): 4 print(123) 5 time.sleep(1) 6 print("end123") 7 8 def bar(): 9 print(456) 10 time.sleep(3) 11 print("end456") 12 13 14 t1=Thread(target=foo) 15 t2=Thread(target=bar) 16 17 t1.daemon=True 18 t1.start() 19 t2.start() 20 print("main-------")
2.5线程锁(互斥锁Mutex)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
1 import time 2 import threading 3 4 5 def addNum(): 6 global num # 在每个线程中都获取这个全局变量 7 print('--get num:', num) 8 time.sleep(1) 9 num -= 1 # 对此公共变量进行-1操作 10 11 12 num = 100 # 设定一个共享变量 13 thread_list = [] 14 for i in range(100): 15 t = threading.Thread(target=addNum) 16 t.start() 17 thread_list.append(t) 18 19 for t in thread_list: # 等待所有线程执行完毕 20 t.join() 21 22 print('final num:', num)
正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
*注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁
1 import time 2 import threading 3 4 5 def addNum(): 6 global num # 在每个线程中都获取这个全局变量 7 print('--get num:', num) 8 time.sleep(1) 9 lock.acquire() # 修改数据前加锁 10 num -= 1 # 对此公共变量进行-1操作 11 lock.release() # 修改后释放 12 13 14 num = 100 # 设定一个共享变量 15 thread_list = [] 16 lock = threading.Lock() # 生成全局锁 17 for i in range(100): 18 t = threading.Thread(target=addNum) 19 t.start() 20 thread_list.append(t) 21 22 for t in thread_list: # 等待所有线程执行完毕 23 t.join() 24 25 print('final num:', num)
1 import threading 2 import time 3 4 def run(n): 5 lock.acquire() 6 global num 7 num +=1 8 time.sleep(1) 9 lock.release() 10 11 12 lock = threading.Lock() 13 num = 0 14 t_objs = [] #存线程实例 15 for i in range(10): 16 t = threading.Thread(target=run,args=("t-%s" %i ,)) 17 t.start() 18 t_objs.append(t) #为了不阻塞后面线程的启动,不在这里join,先放到一个列表里 19 20 for t in t_objs: #循环线程实例列表,等待所有线程执行完毕 21 t.join() 22 23 print("----------all threads has finished...",threading.current_thread(),threading.active_count()) 24 25 print("num:",num)
2.6全局解析锁 GIL VS Lock
机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图来看一下+配合我现场讲给大家,就明白了。
那你又问了, 既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。
2.7递归锁 RLOCK
说白了就是在一个大锁中还要再包含子锁
1 __author__ = "Alex Li" 2 3 import threading, time 4 5 6 def run1(): 7 print("grab the first part data") 8 lock.acquire() 9 global num 10 num += 1 11 lock.release() 12 return num 13 14 15 def run2(): 16 print("grab the second part data") 17 lock.acquire() 18 global num2 19 num2 += 1 20 lock.release() 21 return num2 22 23 24 def run3(): 25 lock.acquire() 26 res = run1() 27 print('--------between run1 and run2-----') 28 res2 = run2() 29 lock.release() 30 print(res, res2) 31 32 33 34 35 num, num2 = 0, 0 36 lock = threading.RLock() 37 for i in range(1): 38 t = threading.Thread(target=run3) 39 t.start() 40 41 while threading.active_count() != 1: 42 print(threading.active_count()) 43 else: 44 print('----all threads done---') 45 print(num, num2)
2.8Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
1 __author__ = "Alex Li" 2 3 import threading, time 4 5 6 def run(n): 7 semaphore.acquire() 8 time.sleep(1) 9 print("run the thread: %s " % n) 10 semaphore.release() 11 12 13 if __name__ == '__main__': 14 semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行 15 for i in range(22): 16 t = threading.Thread(target=run, args=(i,)) 17 t.start() 18 while threading.active_count() != 1: 19 pass # print threading.active_count() 20 else: 21 print('----all threads done---') 22
此类表示仅在经过一定时间后才应运行的操作 。与线程一样,通过调用start()方法启动计时器。
通过调用thecancel()方法可以停止计时器(在其动作开始之前)。
计时器在执行其操作之前将等待的时间间隔可能与用户指定的时间间隔不完全相同。
2.9事件Events
事件是一个简单的同步对象;
该事件代表一个内部标志和线程
可以等待设置标志,或者自己设置或清除标志。
event = threading.Event()
#客户端线程可以等待设置标志
event.wait()
#a服务器线程可以设置或重置它
event.set()
event.clear()
如果设置了标志,则wait方法不会执行任何操作。
如果该标志被清除,则等待将被阻塞,直到它再次被设置为止。
任意数量的线程都可以等待同一事件。
1 """ 2 同进程的一样 3 4 线程的一个关键特性是每个线程都是独立运行且状态不可预测。 5 如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。 6 为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志, 7 它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 8 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真, 9 它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行 10 """ 11 12 event.isSet():返回event的状态值; 13 14 event.wait():如果 event.isSet()==False将阻塞线程; 15 16 event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; 17 18 event.clear():恢复event的状态值为False。
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
1 """ 2 例如,有多个工作线程尝试链接MySQL,我们想要在链接前确保MySQL服务正常才让那些工作线程去连接MySQL服务器, 3 如果连接不成功,都会去尝试重新连接。那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作 4 5 6 """ 7 8 from threading import Thread,Event 9 import threading 10 import time,random 11 def conn_mysql(): 12 count=1 13 while not event.is_set(): 14 if count > 3: 15 raise TimeoutError('链接超时') 16 print('<%s>第%s次尝试链接' % (threading.current_thread().getName(), count)) 17 event.wait(0.5) 18 count+=1 19 print('<%s>链接成功' %threading.current_thread().getName()) 20 21 22 def check_mysql(): 23 print('