🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[toc] # **进程间同步控制**模块 锁 :互斥锁Lock 数据的安全性问题 进程之间的 信号量 : Semaphore 锁+计数器 事件 :通过一个flag来控制进程的阻塞和执行 ## 锁(Lock)的用途 加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行地修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。 ### 互斥锁的原理 互斥锁的意思就是互相排斥,如果把多个进程比喻为多个人,互斥锁的工作原理就是多个人都要去争抢同一个资源:卫生间,一个人抢到卫生间后上一把锁,其他人都要等着,等到这个完成任务后释放锁,其他人才有可能有一个抢到...... **互斥锁的原理,就是把并发改成穿行,降低了效率,但保证了数据安全不错乱** ### 语法和方法 Lock属于multiprocessing模块,常用的方法如下 ~~~ from multiprocessing import Process,Lock #导入 lock.acquire() #加锁 lock.release() #释放锁 ~~~ ### 模拟抢票练习 多个进程共享同一文件,我们可以把文件当数据库,用多个进程模拟多个人执行抢票任务 ~~~ #文件db.txt的内容为:{"count":1} from multiprocessing import Process import time,json def search(name): dic=json.load(open('db.txt')) time.sleep(1) print('\033[43m%s 查到剩余票数%s\033[0m' %(name,dic['count'])) def get(name): dic=json.load(open('db.txt')) time.sleep(1) #模拟读数据的网络延迟 if dic['count'] >0: dic['count']-=1 time.sleep(1) #模拟写数据的网络延迟 json.dump(dic,open('db.txt','w')) print('\033[46m%s 购票成功\033[0m' %name) def task(name): search(name) get(name) if __name__ == '__main__': for i in range(4): #模拟并发4个客户端抢票 name='<路人%s>' %i p=Process(target=task,args=(name,)) p.start() ~~~ 并发运行,效率高,但竞争写同一文件,数据写入错乱,只有一张票,卖成功给了4个人 ~~~ <路人0> 查到剩余票数1 <路人1> 查到剩余票数1 <路人2> 查到剩余票数1 <路人3> 查到剩余票数1 <路人0> 购票成功 <路人1> 购票成功 <路人7> 购票成功 <路人2> 购票成功 ~~~ 加锁处理:购票行为由并发变成了串行,牺牲了运行效率,但保证了数据安全 ~~~ #把文件db.txt的内容重置为:{"count":1} from multiprocessing import Process,Lock import time,json def search(name): dic=json.load(open('db.txt')) time.sleep(1) print('\033[43m%s 查到剩余票数%s\033[0m' %(name,dic['count'])) def get(name): dic=json.load(open('db.txt')) time.sleep(1) #模拟读数据的网络延迟 if dic['count'] >0: dic['count']-=1 time.sleep(1) #模拟写数据的网络延迟 json.dump(dic,open('db.txt','w')) print('\033[46m%s 购票成功\033[0m' %name) def task(name,lock): search(name) with lock: #相当于lock.acquire(),执行完自代码块自动执行lock.release() get(name) if __name__ == '__main__': lock=Lock() for i in range(4): #模拟并发4个客户端抢票 name='<路人%s>' %i p=Process(target=task,args=(name,lock)) p.start() ~~~ 执行结果 ~~~ <路人0> 查到剩余票数1 <路人1> 查到剩余票数1 <路人2> 查到剩余票数1 <路人3> 查到剩余票数1 <路人0> 购票成功 ~~~ ### 互斥锁与join 使用join可以将并发变成串行,互斥锁的原理也是将并发变成穿行,那我们直接使用join就可以了啊,为何还要互斥锁. join是将一个任务整体串行,而互斥锁的好处则是可以将一个任务中的某一段代码串行,比如只让task函数中的get任务串行 ~~~ def task(name,): search(name) # 并发执行 lock.acquire() get(name) #串行执行 lock.release() ~~~ ## 信号量(Semaphore) 信号量是对锁的封装,锁是同一时间只能有一个进程可以使用,信号量是同一个之间可以有指定数量的进程可以使用,简单理解就是锁+计数器实现的 ~~~ Semaphore管理一个内置的计数器, 每当调用acquire()时内置计数器-1; 调用release() 时内置计数器+1; 计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。 ~~~ ### 信号量的使用 信号量的使用和锁基本完全一样,因此直接用下面的争抢KTV的代码来演示 ~~~ from multiprocessing import Process from multiprocessing import Semaphore import time,random def ktv(i,sm): sm.acquire() print('%s来了KTV'%i) time.sleep(random.randint(1,3)) print('>>%s唱完了'%i) sm.release() if __name__=='__main__': sm=Semaphore(2) l=[] for i in range(4): p=Process(target=ktv,args=(i,sm)) l.append(p) for p in l: p.start() print('end......') ~~~ 执行结果 ``` 0来了KTV 1来了KTV >>0唱完了 2来了KTV >>1唱完了 3来了KTV >>2唱完了 >>3唱完了 ``` ## 事件(Event) 默认进程之间是不能直接通向的,而如果程序中的其他进程程需要通过判断某个进程的状态来确定自己下一步的操作,就需要使用threading库中的Event(事件)对象。 事件对象包含一个可设置的信号标志,它允许进程等待某些事件的发生。 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行 ~~~ from threading import Event event.isSet():返回event的状态值; event.wait():如果 event.isSet()==False将阻塞线程; event.set(): 设置event的状态值为True #所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False。 ~~~ ### 红绿灯模型演示 红绿灯是一个典型的需要用到事件的模型,红灯和绿灯需要交替的闪烁,而汽车只有当绿灯时才能通过,如果遇到红灯,需要等待红灯变为绿灯 ~~~ from multiprocessing import Process from multiprocessing import Event import time,random def traffic_ligths(ev): print('当前是红灯', ev.is_set()) while True: time.sleep(2) if ev.is_set(): print('绿灯-->红灯') ev.clear() else: print('红灯-->绿灯') ev.set() def car(ev,i): if not ev.is_set(): print('卡车%s准备通过'%i) ev.wait() print('卡车%s通过'%i) if __name__=='__main__': ev=Event() p1=Process(target=traffic_ligths,args=(ev,)) p1.daemon p1.start() for i in range(5): p2=Process(target=car,args=(ev,i)) p2.start() time.sleep(random.randrange(0,5)) ~~~ 执行结果: ``` 当前是红灯 False 卡车0准备通过 红灯-->绿灯 卡车0通过 绿灯-->红灯 卡车1准备通过 卡车2准备通过 红灯-->绿灯 卡车2通过 卡车1通过 卡车3通过 卡车4通过 绿灯-->红灯 ```