python之进程和线程

(16) 第16章:进程和线程
进程是独立的空间,在一个进程里面的线程,资源是共享的,但是有的线程空间有限,只能容纳2个人,这种情况下,线程就会有锁,互斥锁,有人释放锁后,其他人才能进去这个线程空间执行任务

1、单任务和多任务、线程进程

单核cpu:出现假死机(转个不停) cpu是乱序执行的
单核cpu:多个任务来回快速切换,每个任务执行一个时间片,并不是同时执行
四核cpu:只执行四个任务,理想情况下可以做到同时执行四个任务(实际情况无法做到,任务不止4个)
假设执行多个任务:情况和单核一样,多个任务快速切换,但是效率比单核高(切换的时间轮更小)

线程/进程

**进程: **进程指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。进程是操作系统分配资源的最小单位。
**线程: **为进程中的一个执行流程,一个进程中可以运行多个线程。 线程是进程中任务执行的最小单位,线程是系统独立调度和分派CPU的基本单位。同一进程中的线程共享内存空间。

2、多进程
多进程文章

系统在执行的任务,每个任务就是一个进程,每一个进程至少有一个线程(主线程)(主线程和子线程)

  • **进程:**系统分配资源的最小单位
  • **线程:**线程是在进程中任务执行的最小单位,线程是系统独立调度和分派CPU的基本单位(对于单核CPU,执行多任务,分时复用,不同的时间点切换任务执行,就在每个不同的时间片执行每个任务)
#python中由multiprocessing模块提供进程服务。
from multiprocessing import Process
from multiprocessing import Pool

3、多线程 b’abv’
多线程文章

每个进程的主线程和子线程,python线程提供了两个模块,高级模块threading和低级模块_thread,threading对_thread进行了封装

import threading
import _thread
from concurrent.futures import ThreadPoolExecutor # 线程池

4、分叉技术

win是没有的(很难实现),只是在linux和unix下才有
当前进程完全复制–>生成子线程,也会复制内存的数据,但是地址空间不同 (分叉就是复制,镜像),子进程就是主进程的镜像,彼此是独立的,执行顺序是随机的

  • fork()函数:Unix/Linux操作系统提供了一个fork()系统调用,调用一次返回两次(主进程和子进程),因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
  • 子进程永远返回0, 父进程返回子进程的ID,这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID
  • 用分叉技术,就可以完全绕开GIL锁(全局解析器锁),效率变高,占用了更多的内存空间
    在unix、linux 通过系统函数**fork()**从现在的进程产生另一个进程,不能再win上用
  • Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程

下面的代码是无法在Windows上运行的,执行在unix、linux、max系统上面运行
有了fork调用,一个进程在接到新任务时就可以复制出一个子进程来处理新任务,常见的Apache服务器就是由父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的http请求

import os
print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:
    print('子进程是 (%s) 主进程是 %s.' % (os.getpid(), os.getppid()))
else:
    print(' (%s) 创建子进程 (%s).' % (os.getpid(), pid))

os.fork() #返回两次,在子进程内返回0,在父进程内返回子进程的id
os.getpid() #返回当前进程id
os.getppid() #返回父进程的id

运行结果如下:
Process (876) start...
 (876) 创建子进程 (877).
子进程为 (877) 父进程为 876.

5、单进程实现

from multiprocessing:python实现多进程的模块,可在win开发,部署到linux(服务器基本是linux),产生跨平台进程
from multiprocessing import Process: 进程对象,在主进程(当前代码)构造子进程
进程实现:(若很很多的线程的时候,不要使用,用进程池)
**守护进程:**后台进程,daemon参数为True的是就是守护进程,表示主进程结束后,子进程(守护进程)必须结束,随主进程的退出而退出

from multiprocessing import Process
import os,time
#在现在的进程产生一个进程,下面的函数就是新进程做的任务
def run():
	#获取进程id
	print('子进程id是%s'%os.getpid())#7216
	time.sleep(3)

#新的产生的进程运行的入口一定要在 __main__ 里面执行
#在其他地方执行,如不做特殊处理会报错
if __name__ == '__main__':
	print('主进程id是%s'%os.getpid())
	#将子进程对象传递给Process构造子进程
	new_process = Process(target=run,args=(),daemon=True)
	#daemon=True表示这个子进程是主进程的守护进程
	new_process.start() #开启子进程
	new_process.join() 
	#进程等待,主进程执行完,在等子进程执行完,然后主进程才退出
	#即进程同步
	#如果不加json 主进程结束了 子进程还没结束这种情况就是阻塞 内存空间不能得到释放
	new_process.close()#关闭这个进程

进程实现总结:

  1. **Process(target=run_proc, args=(‘test’,)) **用于创建一个子进程,target指定子进程中任务,args给target指定的任务传参。
  2. **start()**方法启动进程
  3. **join()**方法等待子进程结束后继续往下运行。用于进程间同步。
  4. Process(target=run_proc, args=(‘test’,)daemon=True):daemon=True 守护进程后台进程,子进程随主进程的退出而退出。

6、进程池
进程池:可指定最大进程数目,同时在执行进程,有人先执行完,就把进程放回进程池,就有了空闲进程,后面补上
上述情况就是进程的复用

from multiprocessing import Process,Pool #pool进程池对象
from multiprocessing.dummy import Pool as ThreadPool 
multiprocessing.cpu_count #返回一个虚拟cpu,即逻辑处理器,逻辑核心数
#线程池,在dummy子模块下面
#进程池和线程池只是池对象不同,池对象在模块不同的位置
import os,time
def run(num):
	time.sleep(num)
	print(num**3)#pow(num,3)也是求num的3次方
if __name__ == '__main__':
#主进程
#构造池对象,最大进程数为8个,或者用cpu_count() 返回cpu的核数
pool = Pool(8)
for i in range(9):
	#添加子进程,传入子线程对象,和args()
	#构造子进程,传递子进程函数对象的参数,是一个元组
	#apply_async异步提交任务,一个一个地添加/产生进程
	#pool.apply(...)给进程池分配任务
	#在py3中apply也是调用apply_async异步提交任务
	pool.apply_async(run,args=(i,))
#当关闭线程池(构造完指定的进程后)的时候,不在接受新的进程
pool.close()#停止往进程池中送数据

pool.terminate()# 杀死终止所有的进程
pool.join()#等待所有的任务都执行完,就结束所有进程

如上面进程池有8个进程,但是创建了9个新的进程,所以第9个进程要等第一个执行完后才开始执行,实际上可以不指定进程池的进程数,这个最大的数量就跟当前机器的逻辑处理器有关

总结

1.Pool(n):产生大小为n的进程池。Pool的默认大小是CPU的核数
2.close():把进程池状态改为close,不允许再添加新的任务,但是已经执行了的进程还会继续执行
3.join():父进程等待所有子进程执行完毕,在join之前需调用close()方法
4.p.apply(target,args,kwargs):给进程池分配任务
5.p.apply_async是异步任务。python3中apply也是调用异步apply_async
6.terminate():关闭进程池,强制终止进程池,已经开始的子进程全部被干掉了

获取当前机器的cpu核心数

import psutil #要安装
psutil.cpu_count()#返回逻辑CPU核心数,逻辑处理器数量
psutil.cpu_count(logical=Fasle)#返回真实的核心数

7、子进程
很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。
subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。
下面的例子演示了如何在Python代码中运行命令nslookup www.python.org,这和命令行直接运行的效果是一样的:

import subprocess
print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])
print('Exit code:', r)

运行结果:
$ nslookup www.python.org
Server:     192.168.19.4
Address:    192.168.19.4

#53Non-authoritative answer:
www.python.org    canonical  name = python.map.fastly.net.

Name:    python.map.fastly.net
Address: 199.27.79.223Exit code: 0

如果子进程还需要输入,则可以通过**communicate()**方法输入

import subprocess

print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)

#上面的代码相当于在命令行执行命令nslookup,然后手动输入:
set q=mx
python.org
exit

8、进程间的通讯
进程间的通讯,因为进程间是彼此独立的,每个进程都有自已的内存空间,每个进程之间的执行是相互独立的
Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据
下面是Queue

from multiprocessing import Precess,Queue  #Queue是进程队列,把构造完的进程放进Queue对象
from multiprocessing import Process,Queue
#往进程队伍添加元素
def write(queue):
	for num in range(8):
		#往Queue队列添加元素
		queue.put(num)
		print('添加了%s'%num)
def read(queue):
	while 1:
		#从Queue队列取元素
		num = queue.get()
		print('获取了%s'%num)
		#构造连个进程,添加和获取
if __name__ == '__main__':
	#构造队列对象
	queue = Queue()
	#构造子进程
	w_process = Process(target=write,args=(queue,))
	r_process = Process(target=read,args=(queue,))
	w_process.start()
	r_process.start()
	w_process.join()
	r_process.terminate()#关闭读的进程,一旦执行,就关闭进程(如果没执行完的进程全部干掉)
	print('end')
	#进程之间通过Queue的进程队列实现通讯,数据的交互与通讯

在Unix/Linux下,multiprocessing模块封装了fork()调用,使我们不需要关注fork()的细节。由于Windows没有fork调用,因此,multiprocessing需要“模拟”出fork的效果,父进程所有Python对象都必须通过pickle序列化再传到子进程去,所以,如果multiprocessing在Windows下调用失败了,要先考虑是不是pickle失败了
小结

在Unix/Linux下,可以使用fork()调用实现多进程。
要实现跨平台的多进程,可以使用multiprocessing模块。
进程间通信是通过Queue、Pipes等实现的

9、单线程实现
threading模块

Thread:线程类,这是我们用的最多的一个类,你可以指定线程函数执行或者继承自它都可以实现子线程功能;
Timer:与Thread类似,但要等待一段时间后才开始运行;
Lock:锁原语,这个我们可以对全局变量互斥时使用;
RLock:可重入锁,使单线程可以再次获得已经获得的锁;
Condition:条件变量,能让一个线程停下来,等待其他线程满足某个“条件”;
Event:通用的条件变量。多个线程可以等待某个事件发生,在事件发生后,所有的线程都被激活;
Semaphore:为等待锁的线程提供一个类似“等候室”的结构;
BoundedSemaphore:与semaphore类似,但不允许超过初始值;
Queue:实现了多生产者(Producer)、多消费者(Consumer)的队列,支持锁原语,能够在多个线程之间提供很好的同步支持。

Thread类:是你主要的线程类,可以创建进程实例。该类提供的函数包括:

getName(self):返回线程的名字
isAlive(self):布尔标志,表示这个线程是否还在运行中
isDaemon(self): 返回线程的daemon标志,守护线程
join(self, timeout=None):程序挂起,直到线程结束,如果给出timeout,则最多阻塞timeout秒
run(self):定义线程的功能函数
setDaemon(self, daemonic):把线程的daemon标志设为daemonic
setName(self, name):设置线程的名字
start(self):开始线程执行

Queue提供的类

Queue:队列
LifoQueue:后入先出(LIFO)队列
PriorityQueue:优先队列

Python这门解释性语言也有专门的线程模型,Python虚拟机使用GIL(Global Interpreter Lock,全局解释器锁)来互斥线程对共享资源的访问,但暂时无法利用多处理器的优势

线程的执行是由cpu调度执行的
多线程:一个进程有多个线程(主线程和多个子线程),线程就是操作系统的执行单元
进程:多个线程(主线程和多个子线程)
线程构造:用法和进程一样

低级线程模块 _thread

#调用_thread模块中的start_new_thread() 函数来产生新线程
#语法如下:_thread.start_new_thread ( function, args[, kwargs] )
import _thread  # 低级模块
import random, time
def func_a(a, b):
    tid = _thread.get_ident()
    for i in range(5):
        rnum = random.randint(0, 10)
        time.sleep(0.03)
        print('Thread:%d,generate a random num %d' % (tid, rnum))
    return a + b

t1 = _thread.start_new_thread(func_a, (1, 2))
t2 = _thread.start_new_thread(func_a, (3, 2))

while True:
    pass

总结

1.function - 线程函数。
2.args - 传递给线程函数的参数,他必须是个tuple类型。
3.kwargs - 可选参数

高级线程模块:threading高级模块,封装了_thread、_thread是低级模块

  • 在Python中我们主要是通过thread和 threading这两个模块来实现的,其中Python的threading模块是对thread做了一些包装的,可以更加方便的被使用,所以我们使用 threading模块实现多线程编程
  • 一般来说,使用线程有两种模式,一种是创建线程要执行的函数,把这个函数传递进Thread对象里,让它来执行;另一种是直接从Thread继承,创建一个新的class,把线程执行的代码放到这个新的 class里
from threading import Thread

threading.current_thread() #获取当前线程
def fun(num):
	for n in range(int(num)):
        print(" I come from %s, num: %s" % (threading.currentThread().getName(), n))
#for循环构建多个线程
def main(thread_num):
	thread_list = list()
	for i in range(0, thread_num):
		thread_name = "thread_%s" % i
		new_thread = Thread(target=fun,args=(num,),name=thread_name , daemon=True)
		thread_list.append(new_thread )#循环产生多个线程
	for thread in thread_list:
		thread.start()
	for thread in thread_list:
		thread.join()
if __name__ == "__main__":
    main(3)

总结

1.threading.Thread(target,name,daemon,args,kwargs):创建一个线程. target指定线程任务,name线程名,daemon为True守护线程,args,kwargs 为子线程中任务函数所需参数.
2.start():开启线程
3.json():等待线程结束
4.由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的
threading
模块有个
current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread
,子线程的名字在创建时指定,我们用funThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……
5.线程的属性p = threading.current_thread()

  1. 属性:p.name 线程名、p.ident 线程id、p.daemon 是否为守护线程
  2. 方法:p.getName() 获取线程名、p.is_alive() 获取线程状态、p.isDaemon()判断是否是守护进程

6.threading中有用的几个方法

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

10、自定义线程

import threading

# 自定义类继承自Thread类
class MyThread(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)
		设置当前线程的名称
		self.setName("new" + self.name)
	#线程执行的方法
	def run(self):
		print("I am %s" % self.name)
	#join方法原型如下,这个方法是用来阻塞当前上下文,直至该线程运行结束
	def join(self,timeout=None):
		pass
	#将进程设置收守护进程的方法
	def setDaemon(self):
		pass

if __name__ == "__main__":
	for thread in range(0, 5):
		t = MyThread()
		t.start()

守护线程 setDaemon方法
当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程就分兵两路,当主线程完成想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是,只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon方法,并设置其参数为True。

11、多线程操作共享数据
多个线程操作共享数据,通过Lock锁对象,避免多个线程操作共享数据
同一进程中的线程共享 进程内存空间. 对于同一变量或者资源都可以进行操作
线程之间共享数据最大的危险在于多个线程同时使用资源导致资源抢占,或者数据读写结果混乱的问题,问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期。这种现象称为“线程不安全

多个线程操作共享数据出现了混乱,循环次数越大,混乱越高
通过锁来控制,如线程1获取到锁对象,那么线程1就操作money,线程2就无法进入,因为线程2没有获取到锁
要等线程1操作完money,释放锁后,线程2获取锁才能操作money(有锁的情况下,只有一个线程操作共享数据)

互斥锁:来保证共享数据操作的完整性,每个对象都对应于一个可称为” 互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象,python中的Lock()类就是互斥锁
加锁
Lock()
,先创建锁对象,然后在操作共享数据之前加锁

from threading import Thread,Lock,current_thread
money = 0
#构造锁对象
lock = Lock()
def change_num(time):#次数
	#在局部范围内操作全局
	global money
	#current_thread().name-->获取当前线程名
	print('当前线程是%s'%current_thread().name)
	try:
		#激活Lock对象,加锁,一定要在操作共享数据前加锁
		lock.acquire()
	for i in range(time):
		money +=time
		money -=time
	finally:
		#释放锁,一定要在操作完之后
		lock.release()
		print('action--->',money)
		
#定义两个线程:存钱取钱、存钱取钱
t1 = Thread(target=change_num,args=(99999,))
t2 = Thread(target=change_num,args=(88888,))
#每次执行都是存,在取,理想的结果是0
t1.start()
t2.start()
t1.join()
t2.join()
print('end--->',money)

同步阻塞

当一个线程调用Lock对象的acquire()方法获得锁时,这把锁就进入“locked”状态。因为每次只有一个线程1可以获得锁,所以如果此时另一个线程2试图获得这个锁,该线程2就会变为“block“同步阻塞状态。直到拥有锁的线程1调用锁的release()方法释放锁之后,该锁进入“unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

总结

**Lock()**产生一个锁
RLock(): 可重入锁,使单线程可以再次获得已经获得的锁
lock.acquire():申请使用锁,获得锁,上锁
lock.release():释放锁
更好的方式 with 语句上下文,类似文件操作with open()

12、死锁
通过对公共资源使用互斥锁,这样就简单的到达了我们的目的,但是如果我们又遇到下面的情况:

1、遇到锁嵌套的情况该怎么办,这个嵌套是指当我一个线程在获取临界资源时,又需要再次获取;
2、如果有多个公共资源,在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源;
上述这两种情况会直接造成程序挂起,即死锁,下面我们会谈死锁及可重入锁RLock

死锁概念

所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。 由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。

如下,代码中展示了一个线程的两个功能函数分别在获取了一个竞争资源之后再次获取另外的竞争资源

import threading
  
counterA = 0
counterB = 0
  
mutexA = threading.Lock()
mutexB = threading.Lock()
  
class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
  
    def run(self):
        self.fun1()
        self.fun2()
  
    def fun1(self):
        global mutexA, mutexB
        if mutexA.acquire():
            print("I am %s , get res: %s" %(self.name, "ResA"))
  
            if mutexB.acquire():
                print("I am %s , get res: %s" %(self.name, "ResB"))
                mutexB.release()
  
        mutexA.release()
  
    def fun2(self):
        global mutexA, mutexB
        if mutexB.acquire():
            print("I am %s , get res: %s" %(self.name, "ResB"))
  
            if mutexA.acquire():
                print("I am %s , get res: %s" %(self.name, "ResA"))
                mutexA.release()
  
        mutexB.release()
  
if __name__ == "__main__":
    for i in range(0, 100):
        my_thread = MyThread()
        my_thread.start()

避免死锁

避免死锁主要方法就是:正确有序的分配资源,避免死锁算法中最有代表性的算法是Dijkstra E.W 于1968年提出的银行家算法

可重入锁RLock
考虑这种情况:如果一个线程遇到锁嵌套的情况该怎么办,这个嵌套是指当我一个线程在获取临界资源时,又需要再次获取

在Python中为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源

import threading
import time

counter = 0
mutex = threading.RLock()

class MyThread(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)

	def run(self):
		global counter, mutex
		time.sleep(1);
		if mutex.acquire():
			counter += 1
			print "I am %s, set counter:%s" % (self.name, counter)
			if mutex.acquire():
				counter += 1
				print "I am %s, set counter:%s" % (self.name, counter)
				mutex.release()
		mutex.release()

if __name__ == "__main__":
	for i in range(0, 200):
		my_thread = MyThread()
		my_thread.start()

如何防止死锁有关死锁的文章
你正在写一个多线程程序,其中线程需要一次获取多个锁,此时如何避免死锁问题
当两个线程相互等待对方释放“锁”时就会发生死锁,或者嵌套申请互斥锁时会产生死锁
举例

  • A,B 两个人合租房子, 有1个电饭煲用于蒸米饭, 1口锅用于炒菜. 这两人不一起吃饭,各做各的.
  • A用电饭煲蒸了饭,准备炒菜, B炒了菜准备蒸饭, 谁都不让步, 一直僵持,两人都吃不了饭.
    死锁经典问题: 哲学家就餐问题 ( 5人 一桌, 5只筷子,5碗饭, 单人吃饭必须一双筷子)

如何解决?
1.尽可能保证每一个 线程只能同时保持一个锁
2.常用的死锁检测与恢复的方案是引入看门狗计数器
3.给锁进行排序,比如id排序,获取锁严格按照从小到大的顺序获取,释放反之

使用Condition实现复杂同步
Condition被称为条件变量,除了提供与Lock类似的acquirerelease方法外,还提供了waitnotify方法
使用Condition的主要方式为:线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题
“生产者-消费者”模型来来演示在Python中使用Condition实现复杂同步

import threading
import time

condition = threading.Condition()
products = 0

class Producer(threading.Thread):
	def __init__(self):
	threading.Thread.__init__(self)
	
	def run(self):
		global condition, products
		while True:
			if condition.acquire():
				if products < 10:
					products += 1;
					print "Producer(%s):deliver one, now products:%s" %(self.name, products)
					condition.notify()
				else:
					print "Producer(%s):already 10, stop deliver, now products:%s" %(self.name, products)
					condition.wait()
				condition.release()
				time.sleep(2)

class Consumer(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)
	
	def run(self):
		global condition, products
		while True:
			if condition.acquire():
				if products > 1:
					products -= 1
					print "Consumer(%s):consume one, now products:%s" %(self.name, products)
					condition.notify()
				else:
					print "Consumer(%s):only 1, stop consume, products:%s" %(self.name, products)
					condition.wait();
				condition.release()
				time.sleep(2)

if __name__ == "__main__":
	for p in range(0, 2):
		p = Producer()
		p.start()
	
	for c in range(0, 10):
		c = Consumer()
		c.start()

代码中主要实现了生产者和消费者线程,双方将会围绕products来产生同步问题,首先是2个生成者生产products ,而接下来的10个消费者将会消耗products

另外:Condition对象的构造函数可以接受一个Lock/RLock对象作为参数,如果没有指定,则Condition对象会在内部自行创建一个RLock;除了notify方法外,Condition对象还提供了notifyAll方法,可以通知waiting池中的所有线程尝试acquire内部锁。由于上述机制,处于waiting状态的线程只能通过notify方法唤醒,所以notifyAll的作用在于防止有线程永远处于沉默状态

使用Event实现线程间通信
上面使用Condition对象初步实现了线程间的通讯, 但是更通用的用法就用threading.Event

threading.Event:可以使一个线程等待其他线程的通知,我们把这个Event传递到线程对象中,Event默认内置了一个标志,初始值为False。一旦该线程通过**wait()方法进入等待状态,直到另一个线程调用该Event的set()**方法将内置标志设置为True时,该Event会通知所有等待状态的线程恢复运行

import threading
import time

class MyThread(threading.Thread):
	def __init__(self, signal):
		threading.Thread.__init__(self)
		self.singal = signal
	
	def run(self):
		print "I am %s,I will sleep ..."%self.name
		self.singal.wait()
		print "I am %s, I awake..." %self.name

if __name__ == "__main__":
	singal = threading.Event()
	for t in range(0, 3):
		thread = MyThread(singal)
		thread.start()
		
	print "main thread sleep 3 seconds... "
	time.sleep(3)
	singal.set()

猜你喜欢

转载自blog.csdn.net/weixin_42504453/article/details/84134585