1 进程相关概念
1.1 进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
#狭义定义: 进程是正在运行的程序的实例(an instance of a computer program that is being executed)。 #广义定义: 进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
1.2 同步/异步
同步:就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。
异步:是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。
1.3 阻塞/非阻塞
阻塞和非阻塞这两个概念与程序等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。
1.4 并发/并行
并行 : 并行是指多个任务同时执行,比如两个男人同时在给自己女朋友发微信。
并发 : 并发是多个任务交替轮流使用资源,比如一个男人在给他7个女朋友发微信,只要他发的够快,宏观上来说他在同时聊7个人。
1.5 进程状态与调度
在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。
#(1)就绪(Ready)状态:当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。 #(2)执行/运行(Running)状态:当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。 #(3)阻塞(Blocked)状态:正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
2 Python中使用多进程
运行中的程序就是一个进程。所有的进程都是通过它的父进程来创建的。因此,运行起来的python程序也是一个进程,那么我们也可以在程序中再创建进程。
多个进程可以实现并发效果,也就是说,当我们的程序中存在多个进程的时候,在某些时候,就会让程序的执行速度变快。
2.1 multiprocessing模块
multiprocess不是一个模块而是python中一个操作、管理进程的包。
大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。
2.1.1 multiprocessing.Process介绍
process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。
# Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动) # 强调: 1. 需要使用关键字的方式来指定参数 2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号 # 参数介绍: 1 group参数未使用,值始终为None 2 target表示调用对象,即子进程要执行的任务 3 args表示调用对象的位置参数元组,args=(1,2,'egon',) 4 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18} 5 name为子进程的名称
#1 p.start():启动进程,并调用该子进程中的p.run() #2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法 #3 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁 #4 p.is_alive():如果p仍然运行,返回True #5 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置 2 p.name:进程的名称 3 p.pid:进程的pid 4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可) 5 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。 所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候 ,就不会递归运行了。
2.1.2 使用process模块创建进程
from multiprocessing import Process import os import time def func(args,args2): # 在子进程中执行的 print(args,args2) time.sleep(3) print('子进程:',os.getpid()) print('子进程的子进程:',os.getppid()) print(12345) if __name__ == '__main__': p = Process(target=func,args = ('参数1','参数2')) # 注册到进程,p就是一个进程对象,还没有启动进程 (主进程) p.start() # 启动进程(开启一个子进程) print('*'*10) print('父进程:',os.getpid()) # 查看当前进程的进程号 print('父进程的父进程:',os.getppid()) # 查看当前进程的父进程 (pycharm 的PID) print() # 进程的生命周期 # 主进程 # 子进程 # 开启了子进程的主进程: # 如果主进程自己的代码长,肯定先等待自己的代码执行结束,主进程才结束 # 如果子进程的执行时间长,主进程会在其代码执行完毕之后等待子进程执行完毕后,主进程才结束 # 运行结果: ********** 父进程: 4520 父进程的父进程: 7980 参数1 参数2 子进程: 11812 子进程的子进程: 4520 12345
2.1.3 进程间的数据隔离
进程的运行时数据是互相独立的,不会相互影响。
from multiprocessing import Process import time x = 100 def change(): global x x = 10 print('子进程修改了x,变为%s,子进程结束了!'%x) if __name__ == '__main__': p = Process(target=change) p.start() time.sleep(5) # 此处主进程进入阻塞状态,进程调度给子进程(用不了5s) print(x) 运行结果: 子进程修改了x,变为10,子进程结束了! 100
2.1.4 join
join()方法:优雅的实现父进程等待子进程结束
from multiprocessing import Process import time def task(n): print('这是子进程:{}'.format(n)) time.sleep(n) print('子进程:{}结束了!'.format(n)) if __name__ == '__main__': start_time = time.time() p1 = Process(target=task, args=(1,)) p2 = Process(target=task, args=(2,)) p3 = Process(target=task, args=(3,)) p1.start() p2.start() p3.start() p1.join() p2.join() p3.join() print('我是主进程') print('共耗时:{}'.format(time.time()-start_time)) # 运行结果: 这是子进程:1 这是子进程:2 这是子进程:3 子进程:1结束了! 子进程:2结束了! 子进程:3结束了! 我是主进程 共耗时:3.7704484462738037
2.1.5 守护进程
父进程中将一个子进程设置为守护进程,那么这个子进程会随着主进程的结束而结束。
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
import os import time from multiprocessing import Process class Myprocess(Process): def __init__(self,person): super().__init__() self.person = person def run(self): print(os.getpid(),self.name) print('%s正在和女主播聊天' %self.person) if __name__ == '__main__': p=Myprocess('哪吒') p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行 p.start() time.sleep(10) # 在sleep时查看进程id对应的进程ps -ef|grep id print('sleep了10s后出现') #运行结果: 6852 Myprocess-1 哪吒正在和女主播聊天 sleep了10s后出现
from multiprocessing import Process import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") if __name__ == "__main__": p1=Process(target=foo) p2=Process(target=bar) p1.daemon=True p1.start() p2.start() time.sleep(0.1) print("main-------")#打印该行则主进程代码结束,则守护进程p1应该被终止.#可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止. # 运行结果: main------- 456 end456
2.1.6 socket聊天并发实例
from socket import * from multiprocessing import Process server=socket(AF_INET,SOCK_STREAM) server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) server.bind(('127.0.0.1',8080)) server.listen(5) def talk(conn,client_addr): while True: try: msg=conn.recv(1024) if not msg:break conn.send(msg.upper()) except Exception: break if __name__ == '__main__': #windows下start进程一定要写到这下面 while True: conn,client_addr=server.accept() p=Process(target=talk,args=(conn,client_addr)) p.start() 使用多进程实现socket聊天并发-server
from socket import * client=socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if not msg:continue client.send(msg.encode('utf-8')) msg=client.recv(1024) print(msg.decode('utf-8')) client端
2.1.7 多进程中的其他方法
from multiprocessing import Process import time import random class Myprocess(Process): def __init__(self,person): self.name=person super().__init__() def run(self): print('%s正在和网红脸聊天' %self.name) time.sleep(random.randrange(1,5)) print('%s还在和网红脸聊天' %self.name) p1=Myprocess('哪吒') p1.start() p1.terminate()#关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活 print(p1.is_alive()) #结果为True print('开始') print(p1.is_alive()) #结果为False
from multiprocessing import Process import os import time def func(args,args2): # 在子进程中执行的 print(args,args2) time.sleep(3) print('子进程:',os.getpid()) print('子进程的子进程:',os.getppid()) if __name__ == '__main__': p = Process(target=func,args = ('参数1','参数2')) # 注册到进程,p就是一个进程对象,还没有启动进程 (主进程) p.start() # 启动进程(开启一个子进程) print('*'*10) print('父进程:',os.getpid()) # 查看当前进程的进程号 print('父进程的父进程:',os.getppid()) # 查看当前进程的父进程 (pycharm 的PID) print() # 运行结果: ********** 父进程: 11752 父进程的父进程: 7980 参数1 参数2 子进程: 3212 子进程的子进程: 11752
2.2 进程同步控制 —— 锁、信号量、事件
2.2.1进程锁
当多个进程使用同一份数据资源的时候,就会因为竞争而引发数据安全或顺序混乱问题。
(1)互斥锁介绍
from multiprocessing import Process import time import random def task1(): print('这是 task1 任务'.center(30, '-')) print('task1 进了洗手间') time.sleep(random.randint(1, 3)) print('task1 办事呢...') time.sleep(random.randint(1, 3)) print('task1 走出了洗手间') def task2(): print('这是 task2 任务'.center(30, '-')) print('task2 进了洗手间') time.sleep(random.randint(1, 3)) print('task2 办事呢...') time.sleep(random.randint(1, 3)) print('task2 走出了洗手间') def task3(): print('这是 task3 任务'.center(30, '-')) print('task3 进了洗手间') time.sleep(random.randint(1, 3)) print('task3 办事呢...') time.sleep(random.randint(1, 3)) print('task3 走出了洗手间') if __name__ == '__main__': p1 = Process(target=task1) p2 = Process(target=task2) p3 = Process(target=task3) p1.start() p2.start() p3.start() # 运行结果: ---------这是 task2 任务---------- task2 进了洗手间 ---------这是 task1 任务---------- task1 进了洗手间 ---------这是 task3 任务---------- task3 进了洗手间 task1 办事呢... task1 走出了洗手间 task3 办事呢... task2 办事呢... task2 走出了洗手间 task3 走出了洗手间
from multiprocessing import Process, Lock import time import random # 生成一个互斥锁 mutex_lock = Lock() def task1(lock): # 锁门 lock.acquire() print('这是 task1 任务'.center(30, '-')) print('task1 进了洗手间') time.sleep(random.randint(1, 3)) print('task1 办事呢...') time.sleep(random.randint(1, 3)) print('task1 走出了洗手间') # 释放锁 lock.release() def task2(lock): # 锁门 lock.acquire() print('这是 task2 任务'.center(30, '-')) print('task2 进了洗手间') time.sleep(random.randint(1, 3)) print('task2 办事呢...') time.sleep(random.randint(1, 3)) print('task2 走出了洗手间') # 释放锁 lock.release() def task3(lock): # 锁门 lock.acquire() print('这是 task3 任务'.center(30, '-')) print('task3 进了洗手间') time.sleep(random.randint(1, 3)) print('task3 办事呢...') time.sleep(random.randint(1, 3)) print('task3 走出了洗手间') # 释放锁 lock.release() if __name__ == '__main__': p1 = Process(target=task1, args=(mutex_lock, )) p2 = Process(target=task2, args=(mutex_lock, )) p3 = Process(target=task3, args=(mutex_lock, )) # 释放新建进程的信号,具体谁先启动无法确定 p1.start() p2.start() p3.start() # 运行结果: ---------这是 task2 任务---------- task2 进了洗手间 task2 办事呢... task2 走出了洗手间 ---------这是 task1 任务---------- task1 进了洗手间 task1 办事呢... task1 走出了洗手间 ---------这是 task3 任务---------- task3 进了洗手间 task3 办事呢... task3 走出了洗手间
这种情况虽然使用加锁的形式实现了顺序的执行,但是程序又重新变成串行了,这样确实会浪费了时间,却保证了数据的安全。
(2) 互斥锁示例
# 使用互斥锁,保证数据安全。 import json import time from multiprocessing import Process,Lock def show(): # 查票并发 with open('ticket') as f: dic = json.load(f) print('余票:%s'%dic['ticket']) def buy_ticket(i,lock): # 串行买票 lock.acquire() # 拿钥匙进门,其他进程阻塞,直到钥匙还回来 【谁网速快谁就先拿到钥匙】 with open('ticket') as f: dic = json.load(f) time.sleep(0.1) if dic['ticket'] > 0: dic['ticket'] -= 1 print('