python 多进程

感谢各位大大的文章,小弟衷心感谢,让我理解了python多进程
https://blog.51cto.com/286577399/2051155
https://www.cnblogs.com/lonelyshy/p/9714806.html
https://www.cnblogs.com/kaituorensheng/p/4465768.html#_label0
https://www.cnblogs.com/ailiailan/p/11850710.html
https://jingyan.baidu.com/article/19192ad864a5bfa53e570782.html
https://www.jb51.net/article/173328.htm
https://blog.csdn.net/u011195887/article/details/105478652
https://www.cnblogs.com/WebLinuxStudy/archive/2004/01/13/11776832.html
https://www.cnblogs.com/chenqionghe/p/9674596.html
https://www.jb51.net/article/170581.htm
https://book.apeland.cn/details/419/
https://www.cnblogs.com/WebLinuxStudy/p/11776832.html
https://www.cnblogs.com/xiugeng/p/9003708.html

操作系统介绍

  • 需要先了解操作系统再学习多进程

操作系统的作用:

  • 1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
  • 2:管理、调度进程,并且将多个进程对硬件的竞争变得有序

多道技术

多道技术中的多道指的是多个程序,多道技术的实现是为了解决多个程序竞争或者说共享同一个资源(比如cpu)的有序调度问题,解决方式即多路复用,多路复用分为时间上的复用和空间上的复用。
产生背景:针对单核,实现并发
现在的主机一般是多核,那么每个核都会利用多道技术
有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个cpu中的任意一个,具体由操作系统调度算法决定。

空间上的复用

  • 将内存分为几部分,每个部分放入一个程序,这样,同一时间内存中就有了多道程序。

时间上的复用

  • 当一个程序在等待I/O时,另一个程序可以使用cpu,如果内存中可以同时存放足够多的作业,则cpu的利用率可以接近100%,类似于我们小学数学所学的统筹方法。
    (操作系统采用了多道技术后,可以控制进程的切换,或者说进程之间去争抢cpu的执行权限。这种切换不仅会在一个进程遇到io时进行,一个进程占用cpu时间过长也会切换,或者说被操作系统夺走cpu的执行权限)
  • 强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样才能保证下次切换回来时,能基于上次切走的位置继续运行

进程理论

  • 正在进行的一个过程或者说一个任务。而负责执行任务则是cpu。

进程与程序的区别

  • 程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。
  • 需要强调的是:同一个程序执行两次,那也是两个进程,比如打开暴风影音,虽然都是同一个软件,但是一个可以播放苍井空,一个可以播放饭岛爱。

并发与并行

无论是并行还是并发,在用户看来都是’同时’运行的,不管是进程还是线程,都只是一个任务而已,真是干活的是cpu,cpu来做这些任务,而一个cpu同一时刻只能执行一个任务

并发
  • 是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发

举例(单核+多道,实现多个进程的并发执行):

egon在一个时间段内有很多任务要做:python备课的任务,写书的任务,交女朋友的任务,王者荣耀上分的任务,
但egon同一时刻只能做一个任务(cpu同一时间只能干一个活),如何才能玩出多个任务并发执行的效果?
egon备一会课,再去跟李杰的女朋友聊聊天,再去打一会王者荣耀….这就保证了每个任务都在进行中.

并行
  • 同时运行,只有具备多个cpu才能实现并行

单核下,可以利用多道技术,多个核,每个核也都可以利用多道技术(多道技术是针对单核而言的)
有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,
一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术
而一旦任务1的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算)
可能被分 配给四个cpu中的任意一个去执行

进程的创建(了解)

  • 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)

  • 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)

  • 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)

  • 一个批处理作业的初始化(只在大型机的批处理系统中应用)

无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的:

  • 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
  • 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。

关于创建的子进程,UNIX和windows

  • 相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。
  • 不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。

进程的终止(了解)

  • 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)

  • 出错退出(自愿,python a.py中a.py不存在)

  • 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try…except…)

  • 被其他进程杀死(非自愿,如kill -9)

进程的层次结构

无论UNIX还是windows,进程只有一个父进程,不同的是:

  • 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。
  • 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。

进程的状态

tail -f access.log |grep '404'

执行程序tail,开启一个子进程,执行程序grep,开启另外一个子进程,两个进程之间基于管道’|’通讯,将tail的结果作为grep的输入。
进程grep在等待输入(即I/O)时的状态称为阻塞,此时grep命令都无法运行

其实在两种情况下会导致一个进程在逻辑上不能运行,

  • 进程挂起是自身原因,遇到I/O阻塞,便要让出CPU让其他进程去执行,这样保证CPU一直在工作
  • 与进程无关,是操作系统层面,可能会因为一个进程占用时间过多,或者优先级等原因,而调用其他的进程去使用CPU。

因而一个进程由三种状态
在这里插入图片描述

进程并发的实现(了解)

进程并发的实现在于,硬件中断一个正在运行的进程,把此时进程运行的所有状态保存下来,为此,操作系统维护一张表格,即进程表(process table),每个进程占用一个进程表项(这些表项也称为进程控制块)
在这里插入图片描述
该表存放了进程状态的重要信息:程序计数器、堆栈指针、内存分配状况、所有打开文件的状态、帐号和调度信息,以及其他在进程由运行态转为就绪态或阻塞态时,必须保存的信息,从而保证该进程在再次启动时,就像从未被中断过一样。

僵尸进程和孤儿进程

僵尸进程(有害)

僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。详解如下

我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。
子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。

因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息:

  • 1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)
  • 2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。

孤儿进程(无害)

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

我们来测试一下(创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被init收养,成为孤儿进程,而非僵尸进程),文件内容

import os
import sys
import time

pid = os.getpid()
ppid = os.getppid()
print 'im father', 'pid', pid, 'ppid', ppid
pid = os.fork()
#执行pid=os.fork()则会生成一个子进程
#返回值pid有两种值:
#    如果返回的pid值为0,表示在子进程当中
#    如果返回的pid值>0,表示在父进程当中
if pid > 0:
    print 'father died..'
    sys.exit(0)

# 保证主线程退出完毕
time.sleep(1)
print 'im child', os.getpid(), os.getppid()

"""
执行文件,输出结果:
im father pid 32515 ppid 32015
father died..
im child 32516 1
"""

看,子进程已经被pid为1的init进程接收了,所以僵尸进程在这种情况下是不存在的,存在只有孤儿进程而已,孤儿进程声明周期结束自然会被init来销毁。

僵尸进程危害场景

例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。
严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。
枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。

测试

1、产生僵尸进程的程序test.py内容如下

#coding:utf-8
from multiprocessing import Process
import time,os

def run():
    print('子',os.getpid())

if __name__ == '__main__':
    p=Process(target=run)
    p.start()
    
    print('主',os.getpid())
    time.sleep(1000)

2、在unix或Linux系统上执行:

[root@vm172-31-0-19 ~]# python3  test.py &
[1] 18652
[root@vm172-31-0-19 ~]# 主 1865218653

[root@vm172-31-0-19 ~]# ps aux |grep Z
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root     18653  0.0  0.0      0     0 pts/0    Z    20:02   0:00 [python3] <defunct> #出现僵尸进程
root     18656  0.0  0.0 112648   952 pts/0    S+   20:02   0:00 grep --color=auto Z

[root@vm172-31-0-19 ~]# top #执行top命令发现1zombie
top - 20:03:42 up 31 min,  3 users,  load average: 0.01, 0.06, 0.12
Tasks:  93 total,   2 running,  90 sleeping,   0 stopped,   1 zombie
%Cpu(s):  0.0 us,  0.3 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1016884 total,    97184 free,    70848 used,   848852 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   782540 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                                        
root      20   0   29788   1256    988 S  0.3  0.1   0:01.50 elfin  

3、等待父进程正常结束后会调用wait/waitpid去回收僵尸进程

但如果父进程是一个死循环,永远不会结束,那么该僵尸进程就会一直存在,僵尸进程过多,就是有害的

  • 解决方法一:杀死父进程
  • 解决方法二:对开启的子进程应该记得使用join,join会回收僵尸进程
class Process(object):
    def join(self, timeout=None):
        '''
        Wait until child process terminates
        '''
        assert self._parent_pid == os.getpid(), 'can only join a child process'
        assert self._popen is not None, 'can only join a started process'
        res = self._popen.wait(timeout)
        if res is not None:
            _current_process._children.discard(self)

join方法中调用了wait,告诉系统释放僵尸进程,discard为从自己的children中剔除。

  • 解决方法三:使用signal模块

python 中使用import signal就可以导入模块了
signal(参数一,参数二)

参数一:我们要进行处理的信号。系统的信号我们可以再终端键入 kill -l查看(共64个)。其实这些信号时系统定义的宏。
参数二:我们处理的方式(是系统默认还是忽略还是捕获)。可以写一个handdle函数来处理我们捕获的信号。

SIGCHLD信号

子进程结束时, 父进程会收到这个信号。
如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。
这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)。

SIG_ING

忽略的意思

使用signal(SIGCHLD, SIG_IGN)处理僵尸进程

  • 通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。## SIGCHLD信号
  • 子进程结束时, 父进程会收到这个信号。
  • 如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。
  • 这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)。

使用signal(SIGCHLD, SIG_IGN)处理僵尸进程

  • 通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。

multiprocessing模块

multiprocessing模块介绍

python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。

Python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,>提供了Process、Queue、Pipe、Lock等组件。

需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。

Process类的介绍

语法

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,可用来开启一个子进程
强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

参数介绍:

  • group参数未使用,值始终为None
  • target表示调用对象,即子进程要执行的任务
  • args表示调用对象的位置参数元组,args=(1,2,‘egon’,)
  • kwargs表示调用对象的字典,kwargs={‘name’:‘egon’,‘age’:18}
  • name为子进程的名称

方法介绍:
p.start():

  • 启动进程,并调用该子进程中的p.run()
    p.run():
  • 进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
    p.terminate():
  • 强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
    p.is_alive():
  • 如果p仍然运行,返回True
    p.join([timeout]):
  • 主进程等待p终止(强调:是主进程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间。

属性介绍:
p.daemon:

  • 默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
    p.name:
  • 进程的名称
    p.pid:
  • 进程的pid
多进程之开启进程的两种方式

注意:在windows中Process()必须放到# if name == ‘main’:下

创建并开启子进程的方式一

import time
import random
from multiprocessing import Process


def piao(name):
    print('%s piaoing' % name)
    time.sleep(random.randrange(1, 5))
    print('%s piao end' % name)


if __name__ == '__main__':
    # 实例化得到四个对象
    p1 = Process(target=piao, args=('egon',))  # 必须加,号
    p2 = Process(target=piao, args=('alex',))
    p3 = Process(target=piao, args=('wupeqi',))
    p4 = Process(target=piao, args=('yuanhao',))
    # 调用对象下的方法,开启四个进程
    p1.start()
    p2.start()
    p3.start()
    p4.start()
    print('主')

创建并开启子进程的方式二

import time
import random
from multiprocessing import Process
class Piao(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        print('%s piaoing' %self.name)
        time.sleep(random.randrange(1,5))
        print('%s piao end' %self.name)
if __name__ == '__main__':
    #实例化得到四个对象
    p1=Piao('egon')
    p2=Piao('alex')
    p3=Piao('wupeiqi')
    p4=Piao('yuanhao')
    #调用对象下的方法,开启四个进程
    p1.start() #start会自动调用run
    p2.start()
    p3.start()
    p4.start()
    print('主')
思考

进程之间的内存空间是共享的还是隔离的?下述代码的执行结果是什么?

from multiprocessing import Process
n=100 #在windows系统中应该把全局变量定义在if __name__ == '__main__'之上就可以了
def work():
    global n
    n=0
    print('子进程内: ',n)
if __name__ == '__main__':
    p=Process(target=work)
    p.start()
    print('主进程内: ',n)

答案:

主进程内:  100
子进程内:  0

Process对象的join方法

在主进程运行过程中如果想并发地执行其他的任务,我们可以开启子进程,此时主进程的任务与子进程的任务分两种情况

  • 情况一:在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源。
  • 情况二:如果主进程的任务在执行到某一个阶段时,需要等待子进程执行完毕后才能继续执行,就需要有一种机制能够让主进程检测子进程是否运行完毕,在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用
from multiprocessing import Process
import time
import random
def task(name):
    print('%s is piaoing' %name)
    time.sleep(random.randint(1,3))
    print('%s is piao end' %name)
if __name__ == '__main__':
    p1=Process(target=task,args=('egon',))
    p2=Process(target=task,args=('alex',))
    p3=Process(target=task,args=('yuanhao',))
    p4=Process(target=task,args=('wupeiqi',))
    p1.start()
    p2.start()
    p3.start()
    p4.start()
    # 有的同学会有疑问: 既然join是等待进程结束, 那么我像下面这样写, 进程不就又变成串行的了吗?
    # 当然不是了, 必须明确:p.join()是让谁等?
    # 很明显p.join()是让主进程等待p的结束,卡住的是主进程而绝非子进程p,
    p1.join()
    p2.join()
    p3.join()
    p4.join() # #等待p停止,才执行下一行代码
    print('主')

上述启动进程与join进程可以简写为

p_l=[p1,p2,p3,p4]
for p in p_l:
    p.start()
for p in p_l:
    p.join()

Process对象的其他属性或方法

terminate与is_alive
from multiprocessing import Process
import time
import random
def task(name):
    print('%s is piaoing' %name)
    time.sleep(random.randrange(1,5))
    print('%s is piao end' %name)
if __name__ == '__main__':
    p1=Process(target=task,args=('egon',))
    p1.start()
    p1.terminate()#关闭进程,不会立即关闭,所以is_alive立刻查看的结果可能还是存活
    print(p1.is_alive()) #结果为True
    print('主')
    print(p1.is_alive()) #结果为False
name与pid
from multiprocessing import Process
import time
import random
def task(name):
    print('%s is piaoing' %name)
    time.sleep(random.randrange(1,5))
    print('%s is piao end' %name)
if __name__ == '__main__':
    p1=Process(target=task,args=('egon',),name='子进程1') #可以用关键参数来指定进程名
    p1.start()
    print(p1.name,p1.pid,)

守护进程

主进程创建子进程,然后将该进程设置成守护自己的进程,守护进程就好比崇祯皇帝身边的老太监,崇祯皇帝已死老太监就跟着殉葬了。

关于守护进程需要强调两点:

  • 其一:守护进程会在主进程代码执行结束后就终止
  • 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

如果我们有两个任务需要并发执行,那么开一个主进程和一个子进程分别去执行就ok了,如果子进程的任务在主进程任务结束后就没有存在的必要了,那么该子进程应该在开启前就被设置成守护进程。主进程代码运行结束,守护进程随即终止

from multiprocessing import Process
import time
import random
def task(name):
    print('%s is piaoing' %name)
    time.sleep(random.randrange(1,3))
    print('%s is piao end' %name)
if __name__ == '__main__':
    p=Process(target=task,args=('egon',))
    p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
    p.start()
    print('主') #只要终端打印出这一行内容,那么守护进程p也就跟着结束掉了
思考

思考下列代码的执行结果有可能有哪些情况?为什么?

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()
    print("main-------")

答案:

main-------
456
end456

互斥锁

互斥锁的原理,就是把并发改成穿行,降低了效率,但保证了数据安全不错乱

#由并发变成了串行,牺牲了运行效率,但避免了竞争
from multiprocessing import Process,Lock
import os,time
def work(lock):
    lock.acquire() #加锁
    print('%s is running' %os.getpid())
    time.sleep(2)
    print('%s is done' %os.getpid())
    lock.release() #释放锁
if __name__ == '__main__':
    lock=Lock()
    for i in range(3):
        p=Process(target=work,args=(lock,))
        p.start()
总结

加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行地修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
因此我们最好找寻一种解决方案能够兼顾:

  • 1、效率高(多个进程共享一块内存的数据)
  • 2、帮我们处理好锁问题。
    这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
    队列和管道都是将数据存放于内存中,而队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,因而队列才是进程间通信的最佳选择。
    我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。

队列

进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
queue不适合传大文件,通产传一些消息.

创建队列的类(底层就是以管道和锁定的方式实现):

Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。

参数介绍:

maxsize是队列中允许最大项数,省略则无大小限制。
但需要明确:
    1、队列内存放的是消息而非大数据
    2、队列占用的是内存空间,因而maxsize即便是无大小限制也受限于内存大小

主要方法介绍:

q.put(self, obj, block=True, timeout=None)
参数

  • self :put就相当于是Queue里的一个方法,这个时候q.put就相当于是队列对象q来调用对象的绑定方法,这个参数可以省略即可

  • obj:是我们需要往队列里面放的值

  • block=True :队列如果满了的话,再往队列里放值的话会等待,程序不会结束

  • timeout=None:是再block这个参数的基础上的,当block的值为真的时候,timeout是用来等待多少秒,如果再这个时间里,队列一直是满的,那么程序就会报错并结束(Queue.Full异常)

q.get(self,block=True, timeout=None)
参数

  • self :get就相当于是Queue里的一个方法,这个时候q.get就相当于是队列对象q来调用对象的绑定方法,这个参数可以省略即可

  • block=True :从队列q对象里面取值,如果娶不到值的话,程序不会结束

  • timeout=None:是再block这个参数的基础上的,当block的值为真的时候,timeout是用来等待多少秒,如果再这个时间里,get取不到队列里面的值的话,那么程序就会报错并结束(queue.Empty异常)

put_nowait() 相当于bolok=False,队列满的时候,再放值的时候,程序不等待,不阻塞,直接报错
get_nowait() 相当于bolok=False,当队列里没有值的时候,再取值的时候,程序不等待,不阻塞,程序直接报错
qsize() 变量名.qsize(),消息数量

使用

from multiprocessing import Process,Queue
q=Queue(3)
#put ,get ,put_nowait,get_nowait,full,empty
q.put(1)
q.put(2)
q.put(3)
print(q.full()) #满了
# q.put(4) #再放就阻塞住了
print(q.get())
print(q.get())
print(q.get())
print(q.empty()) #空了
# print(q.get()) #再取就阻塞住了

生产者消费者模型

为什么要使用生产者消费者模型

  • 生产者指的是生产数据的任务,消费者指的是处理数据的任务,在并发编程中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

什么是生产者和消费者模式

  • 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。
  • 生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
  • 这个阻塞队列就是用来给生产者和消费者解耦的

实现

from multiprocessing import Process,Queue
import time,random,os
def consumer(q,name):
    while True:
        res=q.get()
        if res is None:break
        time.sleep(random.randint(1,3))
        print('\033[43m%s 吃 %s\033[0m' %(name,res))
def producer(q,name,food):
    for i in range(3):
        time.sleep(random.randint(1,3))
        res='%s%s' %(food,i)
        q.put(res)
        print('\033[45m%s 生产了 %s\033[0m' %(name,res))
if __name__ == '__main__':
    q=Queue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=(q,'egon','包子'))
    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,'alex'))
    #开始
    p1.start()
    c1.start()
    p1.join()
    q.put(None)
    print('主')

但上述解决方式,在有多个生产者和多个消费者时,我们则需要用一个很low的方式去解决,有几个消费者就需要发送几次结束信号:相当low,例如

from multiprocessing import Process,Queue
import time,random,os
def consumer(q,name):
    while True:
        res=q.get()
        if res is None:break
        time.sleep(random.randint(1,3))
        print('\033[43m%s 吃 %s\033[0m' %(name,res))
def producer(q,name,food):
    for i in range(3):
        time.sleep(random.randint(1,3))
        res='%s%s' %(food,i)
        q.put(res)
        print('\033[45m%s 生产了 %s\033[0m' %(name,res))
if __name__ == '__main__':
    q=Queue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=(q,'egon1','包子'))
    p2=Process(target=producer,args=(q,'egon2','骨头'))
    p3=Process(target=producer,args=(q,'egon3','泔水'))
    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,'alex1'))
    c2=Process(target=consumer,args=(q,'alex2'))
    #开始
    p1.start()
    p2.start()
    p3.start()
    c1.start()
    c2.start()
    p1.join()
    p2.join()
    p3.join()
    q.put(None)
    q.put(None)
    q.put(None)
    print('主')

其实我们的思路无非是发送结束信号而已,有另外一种队列提供了这种机制

JoinableQueue([maxsize])

这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。

参数介绍

  • maxsize是队列中允许最大项数,省略则无大小限制。

方法介绍

JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
q.task_done():

  • 使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
    q.join():
  • 生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止

基于JoinableQueue实现生产者消费者模型

from multiprocessing import Process,JoinableQueue
import time,random,os
def consumer(q,name):
    while True:
        res=q.get()
        time.sleep(random.randint(1,3))
        print('\033[43m%s 吃 %s\033[0m' %(name,res))
        q.task_done() #发送信号给q.join(),说明已经从队列中取走一个数据并处理完毕了
def producer(q,name,food):
    for i in range(3):
        time.sleep(random.randint(1,3))
        res='%s%s' %(food,i)
        q.put(res)
        print('\033[45m%s 生产了 %s\033[0m' %(name,res))
    q.join() #等到消费者把自己放入队列中的所有的数据都取走之后,生产者才结束
if __name__ == '__main__':
    q=JoinableQueue() #使用JoinableQueue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=(q,'egon1','包子'))
    p2=Process(target=producer,args=(q,'egon2','骨头'))
    p3=Process(target=producer,args=(q,'egon3','泔水'))
    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,'alex1'))
    c2=Process(target=consumer,args=(q,'alex2'))
    c1.daemon=True
    c2.daemon=True
    #开始
    p1.start()
    p2.start()
    p3.start()
    c1.start()
    c2.start()
    p1.join()
    p2.join()
    p3.join()
    #1、主进程等生产者p1、p2、p3结束
    #2、而p1、p2、p3是在消费者把所有数据都取干净之后才会结束
    #3、所以一旦p1、p2、p3结束了,证明消费者也没必要存在了,应该随着主进程一块死掉,因而需要将生产者们设置成守护进程
    print('主')

进程池

常用方法

  • apply() 以同步方式添加进程,必须等待上一个进程退出才能执行下一个进程
  • apply_async() 以异步方式添加进程
  • close() 关闭Pool,使其不接受新任务(还可以使用)
  • terminate() 不管任务是否完成,立即终止
  • join() 主进程阻塞,等待子进程的退出,必须在close和terminate后使用

使用方式

使用进程池(非阻塞)
import multiprocessing
import time


def func(msg):
    print("msg:", msg)
    time.sleep(3)
    print("end")


if __name__ == "__main__":
    pool = multiprocessing.Pool(processes=3)
    for i in range(4):
        msg = "hello %d" % (i)
        # 维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去,apply_async 是异步非阻塞的,就是不用等待当前进程执行完毕,随时根据系统调度来进行进程切换。
        pool.apply_async(func, (msg,))  

    print("Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~")
    pool.close()
    pool.join()  # 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    print("Sub-process(es) done.")
"""
执行结果:
Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~
msg: hello 0
msg: hello 1
msg: hello 2
end
msg: hello 3
end
end
end
Sub-process(es) done.
"""

函数解释:

  • apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,apply(func[, args[, kwds]])是阻塞的
  • close() 关闭pool,使其不在接受新的任务。
  • terminate() 结束工作进程,不在处理未完成的任务。
  • join() 主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。

执行说明:创建一个进程池pool,并设定进程的数量为3,range(4)会相继产生四个对象[0, 1, 2, 4],
四个对象被提交到pool中,因pool指定进程数为3,所以0、1、2会直接送到进程中执行,当其中一个执行完事后才空出一个进程处理对象3,
所以会出现输出“msg: hello 3”出现在"end"后。因为为非阻塞,主函数会自己执行自个的,不搭理进程的执行,所以运行完for循环后直接输出"mMsg: hark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~",主程序在pool.join()处等待各个进程的结束。

使用进程池(阻塞)

import multiprocessing
import time


def func(msg):
    print("msg:", msg)
    time.sleep(3)
    print("end")


if __name__ == "__main__":
    pool = multiprocessing.Pool(processes=3)
    for i in range(4):
        msg = "hello %d" % (i)
        pool.apply(func, (msg,))  # 以同步方式添加进程

    print("Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~")
    pool.close()
    pool.join()  # 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束
    print("Sub-process(es) done.")
"""
执行结果:
msg: hello 0
end
msg: hello 1
end
msg: hello 2
end
msg: hello 3
end
Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~
Sub-process(es) done.
"""

说明:apply(func[, args[, kwds]])是阻塞的,与例1有很大差异

使用进程池,并关注结果
  • 收集任务方法返回的结果
import multiprocessing
import time


def func(msg):
    print("msg:", msg)
    time.sleep(3)
    print("end")
    return "done" + msg


if __name__ == "__main__":
    pool = multiprocessing.Pool(processes=4)
    result = []
    for i in range(3):
        msg = "hello %d" % (i)
        result.append(pool.apply_async(func, (msg,)))
    pool.close()
    pool.join()
    for res in result:
        print(":::", res.get())
    print("Sub-process(es) done.")
"""
输出结果:
msg: hello 0
msg: hello 1
msg: hello 2
end
endend

::: donehello 0
::: donehello 1
::: donehello 2
Sub-process(es) done.
"""
同一个进程池执行多个任务
import multiprocessing
import os
import time
import random


def Lee():
    print("\nRun task Lee-%s" % (os.getpid()))  # os.getpid()获取当前的进程的ID
    start = time.time()
    time.sleep(random.random() * 10)  # random.random()随机生成0-1之间的小数
    end = time.time()
    print('Task Lee, runs %0.2f seconds.' % (end - start))


def Marlon():
    print("\nRun task Marlon-%s" % (os.getpid()))
    start = time.time()
    time.sleep(random.random() * 40)
    end = time.time()
    print('Task Marlon runs %0.2f seconds.' % (end - start))


def Allen():
    print("\nRun task Allen-%s" % (os.getpid()))
    start = time.time()
    time.sleep(random.random() * 30)
    end = time.time()
    print('Task Allen runs %0.2f seconds.' % (end - start))


def Frank():
    print("\nRun task Frank-%s" % (os.getpid()))
    start = time.time()
    time.sleep(random.random() * 20)
    end = time.time()
    print('Task Frank runs %0.2f seconds.' % (end - start))


if __name__ == '__main__':
    function_list = [Lee, Marlon, Allen, Frank]
    print("parent process %s" % (os.getpid()))

    pool = multiprocessing.Pool(4)
    for func in function_list:  # for循环传入任务
        pool.apply_async(func)  # Pool执行函数,apply执行函数,当有一个进程执行完毕后,会添加一个新的进程到pool中

    print('Waiting for all subprocesses done...')
    pool.close()
    pool.join()  # 调用join之前,一定要先调用close() 函数,否则会出错, close()执行后不会有新的进程加入到pool,join函数等待素有子进程结束
    print('All subprocesses done.')
"""
输出结果:
parent process 7644
Waiting for all subprocesses done...

Run task Lee-5188

Run task Marlon-7408

Run task Allen-6740

Run task Frank-8704
Task Frank runs 2.21 seconds.
Task Marlon runs 3.04 seconds.
Task Lee, runs 3.54 seconds.
Task Allen runs 29.13 seconds.
All subprocesses done.
"""
使用多个进程池
import time
import random
from multiprocessing import Pool, Manager


# 生产者
def producer(q, i):
    food = 'Spam-%d' % i
    time.sleep(random.uniform(1, 2))
    timeVal = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    print('时间:%s\t生产者:%d\t生产了 Spam-%d' % (timeVal, i, i))
    q.put(food)


# 消费者
def consumer(q, i):
    while True:
        food = q.get()
        if not food: break
        time.sleep(random.uniform(1, 2))
        timeVal = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        print('时间:%s\t消费者: %d\t吃了 %s' % (timeVal, i, food))


if __name__ == '__main__':
    q = Manager().Queue()

    producterNum = 50
    producterPoolNum = 5
    producterPool = Pool(producterPoolNum)
    for n in range(1, producterNum + 1):
        producterPool.apply_async(producer, (q, n))

    consumerPoolNum = 5
    consumerPool = Pool(consumerPoolNum)
    for n in range(1, consumerPoolNum + 1):
        consumerPool.apply_async(consumer, (q, n))

    producterPool.close()
    producterPool.join()

    for n in range(1, consumerPoolNum + 1):
        q.put(None)

    consumerPool.close()
    consumerPool.join()

    print('end')

进程池实现生产者消费者模式

注意:如果要使用Pool(进程池方式)创建进程,就需要使用multiprocessing.Manager()中的 Queue(),而不是multiprocessing.Queue()

import time
import random
from multiprocessing import Pool, Manager


# 生产者
def producer(q, i):
    food = 'Spam-%d' % i
    time.sleep(random.uniform(1, 2))
    timeVal = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    print('时间:%s\t生产者:%d\t生产了 Spam-%d' % (timeVal, i, i))
    q.put(food)


# 消费者
def consumer(q, i):
    while True:
        food = q.get()
        if not food: break
        time.sleep(random.uniform(1, 2))
        timeVal = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        print('时间:%s\t消费者: %d\t吃了 %s' % (timeVal, i, food))


if __name__ == '__main__':
    q = Manager().Queue()

    producterNum = 50
    producterPoolNum = 5
    producterPool = Pool(producterPoolNum)
    for n in range(1, producterNum + 1):
        producterPool.apply_async(producer, (q, n))

    consumerPoolNum = 5
    consumerPool = Pool(consumerPoolNum)
    for n in range(1, consumerPoolNum + 1):
        consumerPool.apply_async(consumer, (q, n))

    producterPool.close()
    producterPool.join()

    for n in range(1, consumerPoolNum + 1):
        q.put(None)

    consumerPool.close()
    consumerPool.join()

    print('end')

各种队列的区别

Python Queue、multiprocessing.Queue、multiprocessing.Manager().Queue()三种消息队列的区别

import Queue
用于线程间的消息队列

from multiprocessing import Queue
用于子进程间的消息队列。但是用于线程间好像也没有问题

from multiprocessing import Manager,Pool
msg_queue = Manager.Queue()
用于进程池Pool的进程之间的消息队列。由一个独立的Python子进程管理该消息队列

应用:使用多进程实现快速拷贝文件

# 导入模块
import os
import multiprocessing
# 拷贝文件函数
def copy_dir(file_name, source_dir, desk_dir):
  # 要拷贝的文件路径
  source_path = source_dir+'/'+file_name
  # 目标路径
  desk_path = desk_dir+'/'+file_name
  # 获取文件大小
  file_size = os.path.getsize(source_path)
  # 记录拷贝次数
  i = 0
  # 以二进制度读方式打开原文件
  with open(source_path, "rb") as source_file:
    # 以二进制写入方式创建并打开目标文件
    with open(desk_path, "wb") as desk_file:
      # 循环写入
      while True:
        # 读取1024字节
        file_data = source_file.read(1024)
        # 如果读到的不为空,则将读到的写入目标文件
        if file_data:
          desk_file.write(file_data)
          # 读取次数+1
          i += 1
          # 拷贝百分比进度等于拷贝次数*1024*100/文件大小
          n = i*102400/file_size
          if n >= 100:
            n = 100
          print(file_name, "拷贝进度%.2f%%" % n)
        else:
          print(file_name, "拷贝成功")
          break
if __name__ == '__main__':
  # 要拷贝的文件夹
  source_dir = 'test'
  # 要拷贝到的路径
  desk_dir = 'C:/Users/Administrator/Desktop/'+source_dir
  # 存在文件夹则不创建
  try:
    os.mkdir(desk_dir)
  except:
    print("目标文件夹已存在,未创建")
  # 获取文件夹内文件目录,存到列表里
  file_list = os.listdir(source_dir)
  print(file_list)
  # 创建进程池,最多同时运行3个子进程
  pool = multiprocessing.Pool(3)
  for file_name in file_list:
    # 异步方式添加到进程池内
    pool.apply_async(copy_dir, args=(file_name, source_dir, desk_dir))
  # 关闭进程池(停止添加,已添加的还可运行)
  pool.close()
  # 让主进程阻塞,等待子进程结束
  pool.join()

进程池与线程池(ProcessPoolExecutor)

在刚开始学多进程或多线程时,我们迫不及待地基于多进程或多线程实现并发的套接字通信,然而这种实现方式的致命缺陷是:服务的开启的进程数或线程数都会随着并发的客户端数目地增多而增多,这会对服务端主机带来巨大的压力,甚至于不堪重负而瘫痪,于是我们必须对服务端开启的进程数或线程数加以控制,让机器在一个自己可以承受的范围内运行,这就是进程池或线程池的用途,例如进程池,就是用来存放进程的池子,本质还是基于多进程,只不过是对开启进程的数目加上了限制

concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用
# 两者都实现了同一个接口,这个接口是由抽象Executor类定义的。

基本方法

1、submit(fn, *args, **kwargs)
异步提交任务

2map(func, *iterables, timeout=None, chunksize=1) 
取代for循环submit的操作

3、shutdown(wait=True) 
相当于进程池的pool.close()+pool.join()操作
wait=True,等待池内所有任务执行完毕回收完资源后才继续
wait=False,立即返回,并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前

4、result(timeout=None)
取得结果

5、add_done_callback(fn)
回调函数

进程池

用法

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import os,time,random
def task(n):
    print('%s is runing' %os.getpid())
    time.sleep(random.randint(1,3))
    return n**2
if __name__ == '__main__':
    executor=ProcessPoolExecutor(max_workers=3)
    futures=[]
    for i in range(11):
        future=executor.submit(task,i)
        futures.append(future)
    executor.shutdown(True)
    print('+++>')
    for future in futures:
        print(future.result())

线程池

把ProcessPoolExecutor换成ThreadPoolExecutor,其余用法全部相同

map方法

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import os,time,random
def task(n):
    print('%s is runing' %os.getpid())
    time.sleep(random.randint(1,3))
    return n**2
if __name__ == '__main__':
    executor=ThreadPoolExecutor(max_workers=3)
    # for i in range(11):
    #     future=executor.submit(task,i)
    executor.map(task,range(1,12)) #map取代了for+submit

回调函数

可以为进程池或线程池内的每个进程或线程绑定一个函数,该函数在进程或线程的任务执行完毕后自动触发,并接收任务的返回值当作参数,该函数称为回调函数

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from multiprocessing import Pool
import requests
import json
import os
def get_page(url):
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}
def parse_page(res):
    res=res.result()
    print('<进程%s> parse %s' %(os.getpid(),res['url']))
    parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
    with open('db.txt','a') as f:
        f.write(parse_res)
if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]
    p=ProcessPoolExecutor(3)
    for url in urls:
        p.submit(get_page,url).add_done_callback(parse_page) 
        #parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果

进程间实现数据共享

Python的进程队列queue和管道pipe只是实现了数据交互,没有实现数据共享
进程的manager方法可以共享数据,比如共享列表,元组,字典,锁,数字,字符串

import multiprocessing


def foo(mlist):
    mlist.append("foo")


if __name__ == "__main__":
    manager = multiprocessing.Manager()
    mlist = manager.list([1, 2, 3])
    ps = multiprocessing.Process(target=foo, args=(mlist,))
    ps.start()
    ps.join()
    print(mlist)

"""
输出结果
[1, 2, 3, 'foo']
"""

map和map_async方法

map()

函数原型:map(func, iterable[, chunksize=None])

Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到结果返回
注意:虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程

map_async()

函数原型:map_async(func, iterable[, chunksize[, callback]])
与map用法一致,但是它是非阻塞的

import multiprocessing


def m1(x):
    print(x)


if __name__ == '__main__':
    pool = multiprocessing.Pool(multiprocessing.cpu_count())
    i_list = range(8)
    pool.map(m1, i_list)
    pool.close()
    pool.join()

python Pipe

多进程里面有个pipe的方法来实现消息队列:

  1. Pipe 方法返回(conn1, conn2)代表一个管道的两端。PIPE方法有个deplex参数,如果deplex参数为True(默认值),那么这个管道是全双工模式,也就是说conn1和conn2均可收发。duplex为False,conn1只负责接收消息,conn2负责发送消息。

2.send 和recv方法分别是发送和接受消息的方法。close方法表示关闭管道,当消息接收结束以后,关闭管道。

案例1

import time
from multiprocessing import Pipe, Process


def proc1(pipe):
    for i in range(1, 10):
        pipe.send(i)
        print("send {0} to pipe".format(i))
        time.sleep(1)


def proc2(pipe):
    n = 9
    while n > 0:
        result = pipe.recv()
        print("recv {0} from pipe".format(result))
        n -= 1


def main():
    pipe = Pipe(duplex=False)
    print(type(pipe))
    p1 = Process(target=proc1, args=(pipe[1],))
    p2 = Process(target=proc2, args=(pipe[0],))  # 接收写0
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    pipe[0].close()
    pipe[1].close()


if __name__ == '__main__':
    main()
"""
输出结果:
<class 'tuple'>
send 1 to pipe
recv 1 from pipe
send 2 to pipe
recv 2 from pipe
send 3 to pipe
recv 3 from pipe
send 4 to pipe
recv 4 from pipe
send 5 to pipe
recv 5 from pipe
send 6 to pipe
recv 6 from pipe
send 7 to pipe
recv 7 from pipe
send 8 to pipe
recv 8 from pipe
send 9 to pipe
recv 9 from pipe
"""

案例2

from multiprocessing import Process, current_process, Queue, Pipe
import time
import pickle


def func_left(q, left):
    for a in range(5000):
        b = q.get()  # 从队列中获取数据
        print('向右管道发送数据', b)
        msg = pickle.dumps(b)  # 给右管道发送数据 数据是序列化之后的数据
        left.send(msg)  # 发送数据


def func_right(q, right):
    for a in range(5000):
        # 从队列中获取数据
        msg = pickle.loads(right.recv())  # 从右管道接受数据 数据是反序列化之后的数据
        print('接受到左管道发送的数据', msg)  # 打印出接受的数据


def main():
    q = Queue()  # 新建立一个queue共享数据
    for a in range(10001):
        q.put(a)
    left, right = Pipe()  # 建立双管道
    p1 = Process(target=func_left, name='左管道', args=(q, left))
    p2 = Process(target=func_right, name='右管道', args=(q, right))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    left.close()
    right.close()


if __name__ == '__main__':
    main()
"""
向右管道发送数据 0
接受到左管道发送的数据 0
向右管道发送数据 1
接受到左管道发送的数据 1
向右管道发送数据 2
接受到左管道发送的数据 2
向右管道发送数据 3
接受到左管道发送的数据 3
向右管道发送数据 4
接受到左管道发送的数据 4
......
接受到左管道发送的数据 4996
向右管道发送数据 4997
接受到左管道发送的数据 4997
向右管道发送数据 4998
接受到左管道发送的数据 4998
向右管道发送数据 4999
接受到左管道发送的数据 4999
"""
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值