进程错乱
多进程共享同一套文件系统,访问同一个文件,或同一个打印终端,是没有问题的
但是进程之间数据不共享,多个进程共同操作数据会出现错乱,我们来看一个例子
from multiprocessing import Process
import os,time
def work():
print('%s is running' %os.getpid())
time.sleep(2)
print('%s is done' %os.getpid())
if __name__ == '__main__':
for i in range(3):
p=Process(target=work)
p.start()
输出如下:
14400 is running
368 is running
13808 is running
14400 is done
368 is done
13808 is done
看到的现象是多个子进程循环执行,由于循环瞬间执行完毕,就可以当作多个子进程同时进行,然后 time.sleep(2),又可以看多个子进程同时剩下的任务代码
进程join引入互斥锁
在介绍互斥锁,我们可以利用join来伪实现下,但是join实现的跟互斥锁是不一样的效果
from multiprocessing import Process,Lock
import os,time,random
def task():
print('%s print 1' %os.getpid())
time.sleep(random.randint(1,3))
print('%s print 2' %os.getpid())
if __name__ == '__main__':
p1=Process(target=task)
p2=Process(target=task)
p3=Process(target=task)
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()
输出如下:
E:\python\python_sdk\python.exe "E:/python/py_pro/上课内容/2 互斥锁-进程.py"
11076 print 1
11076 print 2
1276 print 1
1276 print 2
10316 print 1
10316 print 2
Process finished with exit code 0
通过join操作,从效果上也实现了子进程并行的效果,但是这样的并行效果,是让其中一个子进程运行,在这个子进程运行完毕,在让另外一个子进程运行,所以在其中一个子进程运行期间,其他进程只能按着固有的顺序,我们看下利用互斥锁是如何实现的
进程互斥锁
竞争带来的结果就是错乱,如何控制,就是加锁处理
from multiprocessing import Process,Lock
import os,time,random
def task(mutex):
mutex.acquire()
print('%s print 1' %os.getpid())
time.sleep(random.randint(1,3))
print('%s print 2' %os.getpid())
time.sleep(random.randint(1, 3))
print('%s print 3' %os.getpid())
mutex.release()
print("---------")
if __name__ == '__main__':
mutex=Lock()
p1=Process(target=task,args=(mutex,))
p2=Process(target=task,args=(mutex,))
p3=Process(target=task,args=(mutex,))
p1.start()
print(p1.pid)
p2.start()
p3.start()
print(p3.pid)
输出如下:
E:\python\python_sdk\python.exe "E:/python/py_pro/上课内容/2 互斥锁-进程.py"
2348
1040
10064
2348 print 1
2348 print 2
2348 print 3
---------
1040 print 1
1040 print 2
1040 print 3
---------
10064 print 1
10064 print 2
10064 print 3
---------
Process finished with exit code 0
总结:
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理
购票例子(进程互斥锁)
db.text
{"count": 1}
模拟购票代码
from multiprocessing import Process,Lock
import json
import os
import time
import random
def search():
with open('db.txt',encoding='utf-8') as f:
dic=json.load(f)
print('%s 剩余票数 %s' %(os.getpid(),dic['count']))
def get():
with open('db.txt',encoding='utf-8') as read_f:
dic=json.load(read_f)
if dic['count'] > 0:
dic['count']-=1
time.sleep(random.randint(1,3)) #模拟手速+网速
with open('db.txt','w',encoding='utf-8') as write_f:
json.dump(dic,write_f)
print('%s 抢票成功' %os.getpid())
def task(mutex):
search()
mutex.acquire()
get()
mutex.release()
if __name__ == '__main__':
mutex=Lock()
for i in range(5):
p=Process(target=task,args=(mutex,))
p.start()
输出如下:
E:\python\python_sdk\python.exe "E:/python/py_pro/上课内容/3 模拟抢票.py"
2084 剩余票数 1
11400 剩余票数 1
11736 剩余票数 1
8628 剩余票数 1
14608 剩余票数 1
2084 抢票成功
Process finished with exit code 0
线程互斥锁
from threading import Thread,Lock
import time
n=100
def task():
global n
mutex.acquire()
temp=n
time.sleep(0.1)
n=temp-1
mutex.release()
if __name__ == '__main__':
mutex=Lock()
t_l=[]
for i in range(10):
t=Thread(target=task)
t_l.append(t)
t.start()
for t in t_l:
t.join()
print(n)
输出如下:
90
意思就是启动10个子线程,然后为10个子线程分别加线程互斥锁,操作变量n,然后在各个子线程执行完毕,在输出n的值为90,说明改变了共享变量n
线程锁注意的点如下:
1.线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来
2.join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高
mutex的简写形式
关于互斥锁可以简写成如下:
def task():
global n
mutex.acquire()
temp=n
time.sleep(0.1)
n=temp-1
mutex.release()
def task():
global n
with mutex:
temp=n
time.sleep(0.1)
n=temp-1