并发
一、操作系统发展史
1.穿孔卡片
2.磁带
3.批处理
本质:都是想提高CPU的利用率
二、多道技术
1.空间上的复用
多个程序公用一台硬件设备
2.时间上的复用(洗衣,做饭,烧水)
cpu在多个程序之间来回切换着执行程序
cpu什么时候会切换:
(1)程序占用cpu时间过长,操作系统会剥夺该程序的cpu执行权限(降低了程序的执行效率)
(2)程序遇到IO操作,操作系统会剥夺该程序的cpu执行权限(提高了利用率,并且也不影响程序的执行效率)
并发:看起来像同时运行的就可以
并行:真正意义上的同时执行,单核的计算机能不能实现并行,但是可以实现并发
三、进程理论
1.程序:一堆代码
进程:正在运行的程序
2.同步异步:表示的是任务的提交方式
同步:任务提交之后,原地等待的任务的执行并拿到返回结果才走,期间不做任何事(程序层面的表现就是卡住了)
异步:任务提交之后,不再原地等待,而是继续执行下一行代码(结果是要的,但是是用其他方式获取)
3.阻塞非阻塞:表示的程序的运行状态
阻塞:阻塞态
非阻塞:就绪态,运行态
强调:同步异步,阻塞非阻塞是两对概念,不能混为一谈
四、创建进程的两种方式
1.创建进程就是在内存中重新开辟一块内存空间,
将允许产生的代码丢进去
一个进程对应在内存就是一块独立的内存空间
2.进程与进程之间数据是隔离的,无法直接交互,
但是可以通过某些技术实现间接交互
3.windows创建进程会将代码以模块的方式,从上往下执行一遍
linux会直接将代码完完整整的拷贝一份
windows创建进程一定要在if __name__ == '__main__':代码块内创建,否则报错
4.方法一:
from multiprocessing import Process import time def test(name): print('%s is running'%name) time.sleep(3) print('%s is over'%name) if __name__ == '__main__': p = Process(target=test,args=('egon',)) # 创建一个进程对象 p.start() # 告诉操作系统帮你创建一个进程 print('主')
方式二:
from multiprocessing import Process import time class MyProcess(Process): def __init__(self,name): super().__init__() self.name = name def run(self): print('%s is running' % self.name) time.sleep(3) print('%s is over' % self.name) if __name__ == '__main__': p = MyProcess('egon') p.start() print('主')
五、进程方法join
start()作用仅仅是告诉操作系统帮你创建一个进程,至于这个进程什么时候创建由操作系统随机决定
join()作用主进程代码等待子进程运行结束
from multiprocessing import Process import time def test(name,i): print('%s is running'%name) time.sleep(i) print('%s is over'%name) if __name__ == '__main__': p_list = [] # for i in range(3): # p = Process(target=test,args=('进程%s'%i,i)) # p.start() # p_list.append(p) # for p in p_list: # p.join() p = Process(target=test,args=('egon',1)) p1 = Process(target=test,args=('kevin',2)) p2 = Process(target=test,args=('jason',3)) start_time = time.time() p.start() # 仅仅是告诉操作系统帮你创建一个进程 至于这个进程什么时候创 操作系统随机决定 p1.start() p2.start() p2.join() p.join() p1.join() # 主进程代码等待子进程运行结束 才继续运行 # p.join() # 主进程代码等待子进程运行结束 print('主') print(time.time() - start_time)
六、进程对象及其他方法
查看进程id号
current_process().pid >>> current_process()返回的是Process()
os.getpid() os.getppid()
terminate()作用是杀死当前进程,其实是告诉操作系统帮你杀死一个进程
is_alive()作用是判断进程是否存活
from multiprocessing import Process,current_process import os import time def test(name): # print('%s is running'%name,current_process().pid) print('%s is running'%name,'子进程%s'%os.getpid(),'父进程%s'%os.getppid()) time.sleep(3) print('%s is over'%name) if __name__ == '__main__': p = Process(target=test,args=('egon',)) p.start() p.terminate() # 杀死当前进程 其实是告诉操作系统帮你杀死一个进程 time.sleep(0.1) print(p.is_alive()) # 判断进程是否存活 # print('主',current_process().pid) print('主',os.getpid(),'主主进程:%s'%os.getppid())
七、进程间数据互相隔离
如何验证 开一个子进程修改主进程里面的数据,根本无法修改
from multiprocessing import Process import time money = 100 def test(): global money money = 99999999 if __name__ == '__main__': p = Process(target=test) p.start() p.join() print(money)
八、守护进程
主进程一旦运行完毕,子进程立即结束运行(一起死!)
p.daemon = True作用是将进程设置为守护进程,这一句话必须放在start语句之前,否则报错
from multiprocessing import Process import time def test(name): print('%s总管正常活着'%name) time.sleep(3) print('%s总管正常死亡'%name) if __name__ == '__main__': p = Process(target=test,args=('egon',)) p.daemon = True # 将该进程设置为守护进程 这一句话必须放在start语句之前 否则报错 p.start() time.sleep(0.1) print('皇帝jason寿正终寝')
九、互斥锁
当多个进程操作同一份数据的时候,会造成数据的错乱
这个时候必须加锁处理,将并发变成串行
虽然降低了效率,但是提高了数据的安全
注意:
1.锁不要轻易使用,容易造成死锁现象
2.只在处理数据的部分加锁,不要在全局加锁
锁必须在主进程中产生,交给子进程去使用
mutex.ecquire() # 抢锁 一把锁不能同时被多个人使用,没有抢到的人就一直等待锁的释放
buy(1)
mutex.release() # 释放锁
抢票模拟
from multiprocessing import Process,Lock import time import json # 查票 def search(i): with open('data','r',encoding='utf-8') as f: data = f.read() t_d = json.loads(data) print('用户%s查询余票为:%s'%(i,t_d.get('ticket'))) # 买票 def buy(i): with open('data','r',encoding='utf-8') as f: data = f.read() t_d = json.loads(data) time.sleep(1) if t_d.get('ticket') > 0: # 票数减一 t_d['ticket'] -= 1 # 更新票数 with open('data','w',encoding='utf-8') as f: json.dump(t_d,f) print('用户%s抢票成功'%i) else: print('没票了') def run(i,mutex): search(i) mutex.acquire() # 抢锁 只要有人抢到了锁 其他人必须等待该人释放锁 buy(i) mutex.release() # 释放锁 if __name__ == '__main__': mutex = Lock() # 生成了一把锁 for i in range(10): p = Process(target=run,args=(i,mutex)) p.start()
十、僵尸进程与孤儿进程
子进程结束之后不会立即释放pid等资源信息
主进程释放子进程资源的两种情况:
主进程正常死亡
join方法
1.所有的进程都会步入僵尸进程
父进程回收子进程资源的两种方式
(1)join方法
(2)父进程正常死亡
2.孤儿进程
子进程没死,父进程意外死亡
针对linux会有儿童福利院(init),如果父进程意外死亡他所创建的子进程都会被福利收养