Python3多进程与多线程区别及使用(1.进程)
进程和线程
是什么:
进程是指在系统中正在运行的一个应用程序;程序一旦运行就是进程,或者更专业化来说:进程是指程序执
行时的一个实例。
线程是进程的一个实体。
进程——资源分配的最小单位,线程——程序执行的最小单位。
线程进程的区别体现在几个方面:
第一:因为进程拥有独立的堆栈空间和数据段,所以每当启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这对于多进程来说十分“奢侈”,系统开销比较大,而线程不一样,线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,比进程更节俭,开销比较小,切换速度也比进程快,效率高,但是正由于进程之间独立的特点,使得进程安全性比较高,也因为进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。一个线程死掉就等于整个进程死掉。
第二:体现在通信机制上面,正因为进程之间互不干扰,相互独立,进程的通信机制相对很复杂,譬如管道,信号,消息队列,共享内存,套接字等通信机制,而线程由于共享数据段所以通信机制很方便。。
3.属于同一个进程的所有线程共享该进程的所有资源,包括文件描述符。而不同过的进程相互独立。
4.线程又称为轻量级进程,进程有进程控制块,线程有线程控制块;
5.线程必定也只能属于一个进程,而进程可以拥有多个线程而且至少拥有一个线程;
第四:体现在程序结构上,举一个简明易懂的列子:当我们使用进程的时候,我们不自主的使用if else嵌套来判断pid,使得程序结构繁琐,但是当我们使用线程的时候,基本上可以甩掉它,当然程序内部执行功能单元需要使用的时候还是要使用,所以线程对程序结构的改善有很大帮助。
进程与线程的选择取决以下几点:
1、需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。
2、线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程
序的响应
3、因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;
4、并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求;
5、需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。
多进程
模块:Multiprocessing
(原文有些许错误,本文笔记处已修改,未笔记处自行留意)
简述:
Process类
用来描述一个进程对象
star()方法启动进程
join()方法实现进程间的同步,等待所有进程退出
close()方法用来阻止多余的进程涌入进程池Pool造成进程阻塞
1 multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
target是函数名字
args是函数需要的参数,以tuple的形式传入
import multiprocessing import os def run_proc(name): print('Child process {0} {1} Running '.format(name, os.getpid())) if __name__ == '__main__': print('Parent process {0} is Running'.format(os.getpid())) for i in range(5): p = multiprocessing.Process(target=run_proc, args=(str(i),)) print('process start') p.start() p.join() print('Process close')
结果:
Parent process 5408 is Running
process start
process start
process start
process start
process start
Child process 0 1044 Running
Child process 1 1120 Running
Child process 3 10824 Running
Child process 2 9292 Running
Child process 4 10528 Running
Pool(进程池)
Pool 可以提供指定数量的进程供用户使用,默认是 CPU 核数。当有新的请求提交到 Poll 的
时候,如果池子没有满,会创建一个进程来执行,否则就会让该请求等待。
- Pool 对象调用 join 方法会等待所有的子进程执行完毕
- 调用 join 方法之前,必须调用 close
- 调用 close 之后就不能继续添加新的 Process 了
pool=Pool(numprocess,initializer,initargs)
numproxess:需要创建的进程个数,如果忽略将使用cpu_count()的值。即系统上的CPU数量。
initializer:每个进程启动时都要调用的对象。
initargs:为initalizer传递的参数。
apply_async(要调用的方法,参数列表,关键字参数列表):使用非阻塞方式调用指定方法,并行执行(同时执行)
apply(要调用的方法,参数列表,关键字参数列表):使用阻塞方式调用指定方法,,阻塞方式就是要等上一个进程退出后,下一个进程才开始运行。
close():关闭进程池,不再接受进的进程请求,但已经接受的进程还是会继续执行。
terminate():不管程任务是否完成,立即结束。
join():主进程堵塞(就是不执行join下面的语句),直到子进程结束,注意,该方法必须在close或terminate之后使用。
pool.map(func,iterable,chunksize):将可调用对象func应用给iterable的每一项,然后以列表形式返回结果,
通过将iterable划分为多块,并分配给工作进程,可以并行执行。chunksize指定每块中的项数,
如果数据量较大,可以增大chunksize的值来提升性能。
pool.map_async(func,iterable,chunksize,callback):与map方法不同之处是返回结果是异步的,
如果callback指定,当结果可用时,结果会调用callback。
pool.imap(func,iterable,chunksize):与map()方法的不同之处是返回迭代器而非列表。
pool.imap_unordered(func,iterable,chunksize):与imap()不同之处是:结果的顺序是根据从工作进程接收到的时间而定的。
pool.get(timeout):如果没有设置timeout,将会一直等待结果,
如果设置了timeout,超过timeout将引发multiprocessing.TimeoutError异常。
pool.ready():如果调用完成,返回True
pool.successful():如果调用完成并且没有引发异常,返回True,如果在结果就绪之前调用,将引发AssertionError异常。
pool.wait(timeout):等待结果变为可用,timeout为等待时间。
pool.apply_async
apply_async方法用来同步执行进程,允许多个进程同时进入池子。
import multiprocessing import os import time def run_task(name): print('Task {0} pid {1} is running, parent id is {2}'.format(name, os.getpid(), os.getppid())) time.sleep(1) print('Task {0} end.'.format(name)) if __name__ == '__main__': print('current process {0}'.format(os.getpid())) #设定池内进程数 p = multiprocessing.Pool(processes=3) for i in range(6): p.apply_async(run_task, args=(i,)) print('Waiting for all subprocesses done...') p.close() p.join() print('All processes done!')
结果
current process 562 Task 0 pid 778 is running, parent id is 562 Task 1 pid 779 is running, parent id is 562 Task 2 pid 780 is running, parent id is 562 Waiting for all subprocesses done... Task 1 end. Task 0 end. Task 2 end. Task 3 pid 779 is running, parent id is 562 Task 4 pid 778 is running, parent id is 562 Task 5 pid 780 is running, parent id is 562 Task 4 end. Task 5 end. Task 3 end. All processes done!
pool.apply
apply(func[,args[,kwds]]))
该方法只能允许一个进程进入池子,在一个进程结束后,另一个进程才可以进入池子
import multiprocessing import os import time def run_task(name): print('Task {0} pid {1} is running, parent id is {2}'.format(name, os.getpid(), os.getppid())) time.sleep(1) print('Task {0} end.'.format(name)) if __name__ == '__main__': print('current process {0}'.format(os.getpid())) p = multiprocessing.Pool(processes=3) for i in range(6): p.apply(run_task, args=(i,)) print('Waiting for all subprocesses done...') p.close() p.join() print('All processes done!')
结果:
current process 562 Task 0 pid 785 is running, parent id is 562 Task 0 end. Task 1 pid 786 is running, parent id is 562 Task 1 end. Task 2 pid 787 is running, parent id is 562 Task 2 end. Task 3 pid 785 is running, parent id is 562 Task 3 end. Task 4 pid 786 is running, parent id is 562 Task 4 end. Task 5 pid 787 is running, parent id is 562 Task 5 end. Waiting for all subprocesses done... All processes done!
Queue进程间通信
Queue 用来在多个进程间通信。Queue 有两个方法,get 和 put。
put 方法
Put 方法用来插入数据到队列中,有两个可选参数,blocked 和 timeout。
- blocked = True(默认值),timeout 为正
该方法会阻塞 timeout 指定的时间,直到该队列有剩余空间。如果超时,抛出 Queue.Full 异常。
blocked = False
如果 Queue 已满,立刻抛出 Queue.Full 异常
get 方法
get 方法用来从队列中读取并删除一个元素。有两个参数可选,blocked 和 timeout
- blocked = True(默认),timeout 正值
等待时间内,没有取到任何元素,会抛出 Queue.Empty 异常。
blocked = False
Queue 有一个值可用,立刻返回改值;Queue 没有任何元素,会抛出 Queue.Empty 异常。
from multiprocessing import Process, Queue import os, time, random # 写数据进程执行的代码: def proc_write(q,urls): print('Process(%s) is writing...' % os.getpid()) for url in urls: q.put(url) print('Put %s to queue...' % url) time.sleep(random.random()) # 读数据进程执行的代码: def proc_read(q): print('Process(%s) is reading...' % os.getpid()) while True: url = q.get(True) print('Get %s from queue.' % url) if __name__=='__main__': # 父进程创建Queue,并传给各个子进程: q = Queue() proc_writer1 = Process(target=proc_write, args=(q,['url_1', 'url_2', 'url_3'])) proc_writer2 = Process(target=proc_write, args=(q,['url_4','url_5','url_6'])) proc_reader = Process(target=proc_read, args=(q,)) # 启动子进程proc_writer,写入: proc_writer1.start() proc_writer2.start() # 启动子进程proc_reader,读取: proc_reader.start() # 等待proc_writer结束: proc_writer1.join() proc_writer2.join() # proc_reader进程里是死循环,无法等待其结束,只能强行终止: proc_reader.terminate()
Pipe 进程间通信
常用来在两个进程间通信,两个进程分别位于管道的两端。
multiprocessing.Pipe([duplex])
两个示例
from multiprocessing import Process, Pipe def send(pipe): pipe.send(['spam',42, 'egg']) # send 传输一个列表 pipe.close() if __name__ == '__main__': (con1, con2) = Pipe() # 创建两个 Pipe 实例 sender = Process(target=send, args=(con1, )) # 函数的参数,args 一定是实例化之后的 Pip 变量,不能直接写 args=(Pip(),) sender.start() # Process 类启动进程 print("con2 got: %s" % con2.recv()) # 管道的另一端 con2 从send收到消息 con2.close()
结果
con2 got: ['spam', 42, 'egg']
from multiprocessing import Process, Pipe def talk(pipe): pipe.send(dict(name='Bob', spam=42)) # 传输一个字典 reply = pipe.recv() # 接收传输的数据 print('talker got:', reply) if __name__ == '__main__': (parentEnd, childEnd) = Pipe() # 创建两个 Pipe() 实例,也可以改成 conf1, conf2 child = Process(target=talk, args=(childEnd,)) # 创建一个 Process 进程,名称为 child child.start() # 启动进程 print('parent got:', parentEnd.recv()) # parentEnd 是一个 Pip() 管道,可以接收 child Process 进程传输的数据 parentEnd.send({x * 2 for x in 'spam'}) # parentEnd 是一个 Pip() 管道,可以使用 send 方法来传输数据 child.join() # 传输的数据被 talk 函数内的 pip 管道接收,并赋值给 reply print('parent exit')
结果:
parent got: {'name': 'Bob', 'spam': 42} talker got: {'pp', 'mm', 'aa', 'ss'} parent exit
更多共享数据方式:
multiprocessin:Array,Value (shared memory) 和 Manager(Server process manager)
Server process manager比 shared memory 更灵活,因为它可以支持任意的对象类型。另外,一个单独的manager可以通过进程在网络上不同的计算机之间共享,不过他比shared memory要慢。