进程基础知识
狭义定义:进程是正在运行的程序的实例
广义定义:进程是一个具有一定独立功能的程序对某个数据集合的一次执行活动。
每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量
进程是操作系统进行资源分配最小的操作单元。
并行
: 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用的情况下,比如有三个进程,而CPU是四核 )
并发
: 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。
进程的状态切换图
就绪(Ready)
当进程已被分配到除CPU以外所有必要的资源,只要获得cpu便可立即执行,这时的进程状态称为就绪状态。
执行/运行(Running)
当进程已获得所有必要的资源,其程序正在CPU中执行,此时的进程状态称为执行状态。
阻塞(Blocked)
正在执行的进程,由于等待某个事件发生而无法执行时,便放弃CPU而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
进程管理
Python 提供了一个 multiprocessing 模块,利用它,我们可以来编写跨平台的多进程程序,但需要注意的是 multiprocessing 在 Windows 和 Linux 平台的不一致性:一样的代码在 Windows 和 Linux 下运行的结果可能不同。因为 Windows 的进程模型和 Linux 不一样,Windows 下没有 fork。
启动单个子进程
import os
import time
from multiprocessing import Process
def func(args,args2): #定义一个函数
print(args,args2)
time.sleep(3)
print('子进程id:',os.getpid()) ##打印子进程id
print('父进程id:',os.getppid()) ##打印父进程id
if __name__=='__main__':
p = Process(target=func,args=('参数1','参数2'))
##通过类Process实例化一个子进程对象p
## target=函数名,指定子进程执行的代码。
## args=元组,指定传递给子进程代码的参数。
p.start() ##通知操作系统开启一个子进程,子进程的启动和执行都存在延迟的可能,具体由系统决定。
print('-'*10)
print('主进程id:',os.getpid()) ##打印主进程id
证明子进程是由主进程启动的,默认情况下,子进程同主进程是异步关系,主进程调用子进程后无需等待子进程的结果,继续执行自己的代码。子进程执行完后再通知主进程。
主进程会等待所有子进程结束才结束,并不是执行完主进程自己代码就是结束
启动多个子进程
import os
import time
from multiprocessing import Process
def func(args,args2): #定义一个函数
print(args,args2)
time.sleep(3)
print('%s号的子进程id:'%args,os.getpid()) ##打印子进程id
print('父进程id:',os.getppid()) ##打印父进程id
if __name__=='__main__':
for i in range(5):
p = Process(target=func,args=(i,'参数2'))
##通过类Process实例化一个子进程对象p
## target=函数名,指定子进程执行的代码。
## args=元组,指定传递给子进程代码的参数。
p.start() ##启动子进程p
print('-'*10)
print('主进程id:',os.getpid()) ##打印主进程id
通过for循环实例化多个子进程,每实例化一个子进程后就启动。通过执行结果可知所有子进程和主进程都是异步关系,同时执行。主进程等待所有子进程结束才结束
修改子进程和主进程关系为同步
import time
from multiprocessing import Process
def func(arg1,arg2):
print('*'*arg1)
time.sleep(2)
print('*'*arg2)
if __name__ == '__main__':
p = Process(target=func,args=(10,20))
p.start()
print('hahahaha')
p.join() ##主进程需等待进程p执行结束,才能继续执行主进程后面的代码。
print('====== : 运行完了')
运行结果:
hahahaha
**********
********************
====== : 运行完了
import os
from multiprocessing import Process
def func(filename,content):
with open(filename,'w') as f:
f.write(content*10*'*')
if __name__ == '__main__':
p_lst = []
for i in range(10):
p = Process(target=func,args=('info%s'%i,i))
p_lst.append(p)
p.start()
for p in p_lst:p.join() # 之前的所有子进程都调用join()方法,必须在这里都执行完才能执行下面的代码
print(os.system('ls'))
进程间的数据是隔离的,即使是父进程和子进程
import os
from multiprocessing import Process
def func(lst):
lst.append(1)
print('子进程%s'%os.getpid(),lst)
if __name__ == '__main__':
lst1 = [1,2,3]
p = Process(target=func,args=(lst1,))
p.start()
p.join()
print('父进程%s'%os.getpid(),lst1)
守护进程
import time
import os
from multiprocessing import Process
def func1():
while True:
print('%s父进程代码未执行结束'%os.getppid())
time.sleep(0.2)
def func2():
print('2号子进程开始')
time.sleep(3)
print('2号子进程结束')
p=Process(target=func1)
p.daemon=True ##将子进程设为守护进程
p.start()
p2=Process(target=func2)
p2.start()
print('---'*5)
time.sleep(1)
print('父进程代码结束')
运行结果:
---------------
17584父进程代码未执行结束
2号子进程开始
17584父进程代码未执行结束
17584父进程代码未执行结束
17584父进程代码未执行结束
17584父进程代码未执行结束
父进程代码结束 #此时父进程代码执行结束,但父进程不会关闭,会等待子进程2结束。耗时2秒,在这2秒内守护进程终止。
2号子进程结束
守护进程(子进程1)会随着主进程的代码执行完毕后结束,而不是等待主进程结束(子进程2,是普通子进程未结束时,所以主进程也是未结束)
进程同步管理-进程锁
加锁可以保证多个进程修改同一数据时,只能有一个任务可以进行修改,即串行的修改,速度是慢了,但牺牲了速度却保证了数据安全
import time
from multiprocessing import Process
from multiprocessing import Lock
def buy_ticket(i, lock):
print('%s号子进程准备开始执行'%i)
lock.acquire() ##获取进程锁的钥匙,如果没有钥匙了,则阻塞等待钥匙
print('%s号子进程拿到锁'%i,time.time())
time.sleep(1)
lock.release() ##将获取的钥匙归还,供其他进程获取
if __name__ == '__main__':
lock = Lock() ##实例化一个进程锁1,只有一把钥匙
for i in range(5):
p = Process(target=buy_ticket, args=(i, lock))
p.start()
==运行结果==
0号子进程准备开始执行
1号子进程准备开始执行
2号子进程准备开始执行
3号子进程准备开始执行
4号子进程准备开始执行 #此处之前所有子进程都是进行,后面开始子进程进行抢钥匙lock.acquire()。
0号子进程拿到锁 1552896229.739412
1号子进程拿到锁 1552896230.740792
2号子进程拿到锁 1552896231.7421892
3号子进程拿到锁 1552896232.742517
4号子进程拿到锁 1552896233.743973
进程同步管理-进程的信号量
实现某一段代码在同一时间只能被n个进程执行
import time
import random
from multiprocessing import Process
from multiprocessing import Semaphore
def ktv(i,sem):
sem.acquire() #获取其中一把钥匙,没有钥匙的话则阻塞等待钥匙
print('%s走进ktv'%i)
time.sleep(random.randint(1,5))
print('%s走出ktv'%i)
sem.release() ##归还钥匙
if __name__ == '__main__' :
sem = Semaphore(4) ##实例化一个信号量,实质类似一个有4把钥匙的锁
for i in range(10):
p = Process(target=ktv,args=(i,sem))
p.start()
进程同步管理-进程的事件event
通过一个信号来控制多个进程,同时执行或者阻塞,则称之为事件
from multiprocessing import Event
# 一个信号可以使所有的进程都进入阻塞状态
# 也可以控制所有的进程解除阻塞
# 一个事件被创建之后,默认是阻塞状态
e = Event() # 创建了一个事件默认被设置成false
print(e.is_set()) # 查看一个事件的状态, 输出false
e.set() # 将这个事件的状态改为True,
print(e.is_set()) #输出true
e.wait() # 是依据e.is_set()的值来决定是否阻塞的,false则阻塞,true则不阻塞
print(123456) ##因为上一行判断不阻塞,则执行此代码输出结果
e.clear() # 将这个事件的状态改为False
print(e.is_set()) ##输出false
e.wait() # 判断为事件为阻塞,等待事件的信号被变成True,所以后面的代码不执行
print('*'*10)
# set 和 clear 分别用来修改一个事件的状态 True或者False
# is_set 用来查看一个事件的状态
# wait 是依据事件的状态来决定自己是否在wait处阻塞,False阻塞 True不阻塞
import time
import random
from multiprocessing import Event,Process
def cars(e,i):
if not e.is_set(): ##事件为false则输出在等待
print('car%i在等待'%i)
e.wait() #子进程执行到此处则阻塞 直到得到事件状态变成 True 的信号
print('\033[0;32;40mcar%i通过\033[0m' % i)
def light(e):
while True:
if e.is_set(): ##若事件状态为True,则将事件修改为false,并输出红灯亮
e.clear()
print('\033[31m红灯亮了\033[0m')
else: ##若事件为false 则将事件设为True,并输出绿灯亮
e.set()
print('\033[32m绿灯亮了\033[0m')
time.sleep(2)
if __name__ == '__main__':
e = Event() ##创建一个事件,默认为false 阻塞状态
traffic = Process(target=light,args=(e,))
traffic.start()
##并发20个子进程代表20辆车
for i in range(20):
car = Process(target=cars, args=(e,i))
car.start()
time.sleep(1)
##交通灯的子进程和汽车子进程属于并发状态,汽车子进程通行与否依赖事件,而事件需要交通子进程才能改变
时间 | 事件 | 灯 | 车进程 |
---|---|---|---|
0-2s | false->true | 绿灯 | car0、car1进程启动执行通过 |
2-4s | true->false | 红灯 | car2、car3进程启动执行等待阻塞着 |
4-6s | false->true | 绿灯 | car2、car3进程执行阻塞结束执行通过;car4、car5进程启动执行通过 |
6-8s | true->false | 红灯 | car6、car7进程启动执行等待阻塞着 |
进程池的应用
进程的启动和销毁都需要消耗时间。因为cpu的有限,进程还需要操作系统调度,一旦进程数量多会严重影响操作系统的调度,最终导致效率低。
进程池的引入就是为了只使用一定数量进程数,去处理大量的任务。先创建一定的进程,有需求来了,就拿池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归还进程池,拿到空闲进程才能继续执行。
map方法
,默认会阻塞,等待任务全部结束.如果需要给任务传递多个参数,那么多个参数需要组成一个元组,多个元组组成一个可迭代对象。
import time,os
from multiprocessing import Pool,Process
def func(n):
print(n+1)
print(os.getpid())
##通过进程池创建5个子进程,来处理50个任务
start = time.time()
pool = Pool(5) # 进程池启动5个进程
pool.map(func,range(50)) #map方法的第一个参数为任务函数名;第二个参数必须是可迭代对象。将迭代对象依次取出传入到func中执行。
t1 = time.time() - start
##启动50个子进程来处理50个任务
start = time.time()
p_lst=[]
for i in range(50):
p=Process(target=func,args=(i,))
p.start()
p_lst.append(p)
for p in p_lst:p.join()
t2 = time.time() - start
print(t1,t2)
#观察输出时间,可知进程池的效率更高
apply()
方法向进程池的一个空闲进程发配一个任务执行,主进程会阻塞,等待此方法结束。
import os
import time
from multiprocessing import Pool
def func(n):
print('start func%s'%n,os.getpid())
time.sleep(1)
print('end func%s' % n,os.getpid())
if __name__ == '__main__':
p = Pool(5)
for i in range(10):
p.apply(func,args=(i,))
#此方法向进程池的一个空闲进程发配一个任务执行,主进程会阻塞,等待此方法结束。
print('任务%d结束'%i)
print('******')
p.close() # 关闭进程池接收任务的入口
apply_async()
方法向池里的空闲进程分配任务执行,主进程不会发生阻塞
import os
import time
from multiprocessing import Pool
def func(n):
print('start func%s'%n,os.getpid())
time.sleep(1)
print('end func%s' % n,os.getpid())
if __name__ == '__main__':
p = Pool(5)
for i in range(10):
p.apply_async(func,args=(i,))
#此方法向池里的空闲进程分配任务执行,主进程不等待此方法结束执行后面的代码。
print('任务%d结束' % i)
print('***')
p.close() # 结束进程池接收任务
p.join() # 发生阻塞,持续到感知进程池中的任务执行结束
#进程池 p.apply的返回值
import time
from multiprocessing import Pool
def func(i):
time.sleep(0.5)
print('正在执行任务%d'%i)
return i*i
if __name__ == '__main__':
p = Pool(5)
for i in range(10):
res = p.apply(func,args=(i,)) #使用apply方法调用的进程池执行任务后会将func的返回值作为apply()方法返回值
print(res)
#进程池 p.apply_async的返回值
import time
from multiprocessing import Pool
def func(i):
time.sleep(1)
print('正在执行任务%d'%i)
return i*i
if __name__ == '__main__':
p = Pool(5)
res_l = []
for i in range(10):
res = p.apply_async(func,args=(i,)) #使用apply_async方法调用的进程池执行任务后#apply_async()方法返回值是一个对象,func任务执行后得到返回值写入这个对象
res_l.append(res) ##对象组成一个列表
print('队列长度%d'%len(res_l))
for res in res_l:
print(res.get()) #res对象的get方法会发生阻塞,直到对象被写入值然后取出。
运行结果:
队列长度1
队列长度2
队列长度3
队列长度4
队列长度5
队列长度6
队列长度7
队列长度8
队列长度9
队列长度10
(睡眠大约一秒,在此之前进程池5个进程被apply_async调用在执行任务0-4,任务5-9在等待进程池空闲)
正在执行任务0正在执行任务2正在执行任务3正在执行任务4正在执行任务1
(任务0-4执行完毕,将返回值写入相应的对象res,然后执行任务5-9)
0
1
4
9
16
(睡眠大约一秒)
正在执行任务7正在执行任务5正在执行任务6正在执行任务8正在执行任务9
25
36
49
64
81
# 回调函数
适用爬虫等场景,获取数据的过程由进程池的子进程完成,获取数据的处理交给主进程处理。
import os
from multiprocessing import Pool
def func1(n):
print('in func1',os.getpid())
return n*n
def func2(nn):
print('in func2',os.getpid())
print(nn)
if __name__ == '__main__':
print('主进程 :',os.getpid())
p = Pool(5)
for i in range(10):
p.apply_async(func1,args=(i,),callback=func2) ##将func1的返回值传给回调函数func2执行,回调函数由主进程执行,func1由
##进程池中的进程执行。
p.close()
p.join()