前言:计算机时代发展迅速,从单核cpu发展为多核的cpu,从原始的单进程转变成现在的多进程执行任务,那么什么是多进程呢?要怎样使用呢,那么我来介绍一下吧!!!
进程:
1.进程的概念:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程是系统进行资源分配的基本单位,一台机器上可以有多个进程,每个进程执行不同的程序,每个进程相对独立,拥有自己的内存空间,隔离性和稳定性比较高,进程之间互不影响,但是资源共享相对麻烦,系统资源占用相对高,同时进程可以利用cpu多核资源,适合cpu密集型任务!
2.进程的分类
进程又分为单进程和多进程两种形式:
(1).单进程:
单进程:默认程序开启时创建一个进程,咱们所写的一个py文件就是一个进程,系统会分配资源开启一个进程去处理这个py文件,主线程去执行这个文件
(2).多进程
完成多任务,每一个进程各自执行各自的任务,各自互不干扰各干各的事儿,本文章主要讲多进程
3.进程的使用
进程使用需要multiprocessing库,往往是先声明进程实例,里面可以传入消费方法名称和不定长参数args,然后将实例放入指定进程数的容器中(list),通过循环或者列表推导式,使用start方法开启进程,join方法阻塞主进程。
(1).Process参数解析:
- group:指定进程组,一般目前初学者也没有进程组就默认为None;
- target:执行目标任务名;
- name:进程名字;
- args:以元组形式向执行任务传参;
- kwargs:以字典形式向执行任务传参.
(2).Process创建实例对象:
- start():启动子进程实例,也就是创建一个子进程;
- join():等待主进程执行结束,监测主进程完成后,从而再去执行子进程;
- terminate():不管任务是否完成,立即终止子进程.
(3).举例:
from multiprocessing import Process
import time
def job(num):
time.sleep(1)
print("子进程启动{}".format(num))
if __name__ == '__main__':
[Process(target=job,args=(x,)).start() for x in range(100)]
pool.close()
pool.join() #等待主进程执行结束,监测主进程完成后,从而再去执行子进程;
pool.terminate() #不管任务是否完成,立即终止子进程.
4.进程的应用场景
进程可以利用cpu多核资源,适合cpu密集型任务,比如一些统计计算任务,比如计算广告转化率,uv、pv等等,或者一些视频的压缩解码任务,进程还有一个使用场景,就是后期部署项目的时候,nginx反向代理后端服务,往往需要开启多个tornado服务来支持后台的并发,就是利用了多进程的互不干扰,就算某个进程僵死,也不会影响其他进程。
5.进程间的通信
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。
队列queue:
不同于线程queue,进程queue的生成是用multiprocessing模块生成的。
在生成子进程的时候,会将代码拷贝到子进程中执行一遍,及子进程拥有和主进程内容一样的不同的名称空间。
multiprocess.Queue 是跨进程通信队列
常用方法
q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
q.get_nowait():同q.get(False)
q.put_nowait():同q.put(False)
q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
管道queue:
管道就是管道,就像生活中的管道,两头都能进能出
默认管道是全双工的,如果创建管道的时候映射成False,左边只能用于接收,右边只能用于发送,类似于单行道
import multiprocessing
def foo(sk):
sk.send('hello world')
print(sk.recv())
if __name__ == '__main__':
conn1,conn2=multiprocessing.Pipe() #开辟两个口,都是能进能出,括号中如果False即单向通信
p=multiprocessing.Process(target=foo,args=(conn1,)) #子进程使用sock口,调用foo函数
p.start()
print(conn2.recv()) #主进程使用conn口接收
conn2.send('hi son') #主进程使用conn口发送
常用方法
conn1.recv():接收conn2.send(obj)发送的对象。如果没有消息可接收,recv方法会一直阻塞。如果连接的另外一端已经关闭,那么recv方法会抛出EOFError。
conn1.send(obj):通过连接发送对象。obj是与序列化兼容的任意对象
注意:send()和recv()方法使用pickle模块对对象进行序列化
6.进程池
开多进程是为了并发,通常有几个cpu核心就开几个进程,但是进程开多了会影响效率,主要体现在切换的开销,所以引入进程池限制进程的数量。进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
from multiprocessing import Process,Pool
import time
def job(num):
time.sleep(1)
print("子进程启动{}".format(num))
if __name__ == '__main__':
pool = Pool(4)
# [Process(target=job,args=(x,)).start() for x in range(100)]
[pool.apply_async(func=job,args=(x,)) for x in range(100)]
pool.close()
pool.join()
pool.terminate()
结果:
7.进程的状态
1)就绪状态(Ready):
进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。
2)运行状态(Running):
进程占用处理器资源;处于此状态的进程的数目小于等于处理器的数目。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。
3)阻塞状态(Blocked):
由于进程等待某种条件(如I/O操作或进程同步),在条件满足之前无法继续执行。该事件发生前即使把处理器资源分配给该进程,也无法运行
8.线程在进程中
通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。