一、并发编程之——进程
什么是并发编程?就是一个程序可以在同一时刻做多件事情,也就是解决程序中的IO操作影响程序效率的问题。
1.进程
什么是进程?即运行中的程序。且是计算机中最小的资源分配单位。
在python中,每一个运行中的程序,都是一个进程,一个进程就能做一件事,如果有多个进程,就可以做多件事。那么如何在Python中开启一个进程呢?
import os import time from multiprocessing import Process def func(num): print(num,os.getpid()) time.sleep(10) if __name__ == '__main__': print(os.getpid()) p = Process(target=func, args=(10,)) # 创造了一个进程 p.start() # 开启进程 print(os.getpid())
这里引入几个概念:
子进程:即p
主进程:即运行的这个程序
父进程:即创造子进程的进程
同步和异步:
所谓同步就像是做饭和洗衣服不能同时做,而异步像是不衣服放进洗衣机,一边洗衣服一边做饭。所以,进程和进程之间是一步的。异步可以有效地提高程序的效率。
2.守护进程
什么是守护进程?守护进程也是一个子进程,当主进程的代码执行完毕之后自动结束的子进程叫做守护进程。
import time def deamon_func(): while True: print('我还活着') time.sleep(0.5) def wahaha(): for i in range(10): time.sleep(1) print(i*'#') if __name__ == '__main__': p2 = Process(target=wahaha) p2.start() p = Process(target=deamon_func) p.daemon = True p.start() for i in range(3): print(i*'*') time.sleep(1) p2.join()
代码如上图,deamon_func就是一个守护进程,当wahaha执行完毕,即主进程代码执行完毕之后自动结束的子进程为守护进程。
3.锁
什么是锁?为什么要引入锁的概念?
上面我们解决了可以让多个任务并发进行的问题,但同时也迎来了新的问题,在并发运行程序的同时,他们之间是没有顺序的,也不受我们控制,所以很容易引发数据安全和顺序错乱的问题,下面是一个抢票实例,如果多进程同时进行的话,得拿到票的人数极有可能会大于票总数。所以这时我们引入了锁,多个进程在并发执行一个程序时,就像多个人要进屋子拿东西,第一个人进去后,会将门锁上,这时其他人就无法进入屋子,第一个人拿完东西(代码执行完毕)后,打开门,把钥匙挂在门上,第二个人再拿钥匙进去,把门锁上......以此类推。下面代码的执行结果就是只有5个人抢到票。这样加入锁后,降低了运行效率,但大大提高了数据安全性。
#文件db的内容为:{"count":5} #注意一定要用双引号,不然json无法识别 from multiprocessing import Process,Lock import time,json,random def search(): dic=json.load(open('db')) print('\033[43m剩余票数%s\033[0m' %dic['count']) def get(): dic=json.load(open('db')) time.sleep(random.random()) #模拟读数据的网络延迟 if dic['count'] >0: dic['count']-=1 time.sleep(random.random()) #模拟写数据的网络延迟 json.dump(dic,open('db','w')) print('\033[32m购票成功\033[0m') else: print('\033[31m购票失败\033[0m') def task(lock): search() lock.acquire() get() lock.release() if __name__ == '__main__': lock = Lock() for i in range(100): #模拟并发100个客户端抢票 p=Process(target=task,args=(lock,)) p.start()
4.信号量
什么是信号量?
上面所说的锁,是在同一时间内,只有一个线程进行对数据进行修改。而Semaphore信号量是同时允许一定数量的线程更改数据。假设共有4个ktv小屋,13个用户想进去唱歌,由于我们说过开启进程、线程都是有时间开销的,所以在开放小屋时,四个用户进入小屋,后面的用户只能在等有人出来了才能进去。代码如下:
from multiprocessing import Process,Semaphore import time,random def go_ktv(sem,user): sem.acquire() print('%s 占到一间ktv小屋' %user) time.sleep(random.randint(0,3)) #模拟每个人在ktv中待的时间不同 sem.release() if __name__ == '__main__': sem=Semaphore(4) p_l=[] for i in range(13): p=Process(target=go_ktv,args=(sem,'user%s' %i,)) p.start() p_l.append(p) for i in p_l: i.join() print('============》')
5.事件
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
clear:将“Flag”设置为False;
set:将“Flag”设置为True。
事件主要作用场景:红绿灯
6.进程池
为什么要有进程池?进程池的概念。
在程序实际处理问题过程中,忙时会有成千上万的任务需要被执行,闲时可能只有零星任务。那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?首先,创建进程需要消耗时间,销毁进程也需要消耗时间。第二即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。因此我们不能无限制的根据任务开启或者结束进程。那么我们要怎么做呢?
在这里,要给大家介绍一个进程池的概念,定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果。