Python多进程

现在, 多核CPU已经非常普及了, 但是, 即使过去的单核CPU, 也可以执行多任务。 CPU执行代码都是顺序执行的, 那么, 单核CPU是怎么实现多任务的呢?

答案就是操作系统轮流让各个任务交替执行, 任务1执行0.01秒, 切换到任务2, 任务2执行0.01秒, 再切换到任务3, 执行0.01秒……这样反复执行下去。

从CPU的工作状态上看, 每个任务都是交替着单独执行的, 但是, 由于CPU的执行速度非常快, 我们感觉就像所有任务都在同时执行一样。其实真正的并发执行多任务只能在多核CPU上实现, 但是, 由于任务数量远远多于CPU的核数量, 所以, 操作系统也会自动把很多任务轮流调度到每个核上交替执行。

程序和进程的差别不言而喻,编写完毕的代码,在没有运行的时候,称之为程序,正在运行着的代码,就成为进程。进程除了包含代码以外,还有需要运行的环境等。

进程的五态模型:

一、在Linux的Ubuntu系统下创建多进程:

Python的os模块封装了常见的系统调用,其中就包括创建子进程的fork函数。fork这个系统函数非常特殊,普通的函数调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的pid。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。需要注意的是,fork函数只在Unix/Linux/Mac上运行,windows不支持。

1、看一个Ubuntu下创建多进程的例子:

仔细看上述代码和运行结果,程序的执行结果并不是你想象的打印了四次,在这里需要说明的是,当主进程(9584)执行到第一条fork语句时,要创建一个子进程,在这个时候,会将主进程的所有内容,复制一份给子进程(9585),也就说,子进程(9585)中也会包含主进程(9584)中的第二条fork语句,即子进程(9585)也会再创建子进程(9587),主进程(9584)执行到第二条fork语句时,会再创建一个子进程(9586),这样就导致了这段代码会有六次的print,这样说可能依然不是很清楚,下面画一个简单的进程关系图帮助理解:

主进程中有两个fork函数的执行族谱

说明:主进程、 子进程的执行顺序没有规律, 完全取决于操作系统的调度算法

2、多进程修改全局变量

多进程中,子进程相当于是主进程的一个深拷贝, 每个进程中所有数据( 包括全局变量) 都各有拥有一份, 互不影响。

二、Windows下实现多进程

如果你打算编写多进程的服务程序, Unix/Linux无疑是正确的选择。 由于Windows没有fork函数, 难道在Windows上就无法用Python编写多进程的程序?由于Python是跨平台的, 当然也应该提供跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了一个Process类用来创建进程对象, 下面将介绍两种通过multiprocessing这种跨平台的方式实现多进程的方法。

1、创建Process类的进程对象

2、定义类,让其继承于Process类,并重写其run(名称不能改)方法,创建定义的类的实例对象

在这里要特别注意一下,在定义类时,其初始化函数一定要调用Process类的__init__方法(很重要),因为在Process类中,其__init__函数其中有一个关键字参数target,如果不给target赋值,即不指定子进程函数的话,会默认执行其run方法,此处必须调用Process类的__init__函数,并且不给target赋值,因为新定义的类中重写了run方法,所以其中的run方法才会执行。

看一下Process类的help信息,其初始化魔法方法中除了target,args,kwargs等此参数之外还有一个daemon参数,该参数代表该进程是否是守护进程,若daemon=True,则表示该进程为守护进程,当主进程执行完毕时,若只剩下该守护进程,则该守护进程即使没有执行完毕也自动退出,若主进程执行完毕时,还有其他非守护进程未执行完毕,则该守护进程不会退出。

下面看一个例子对比一下是否设置为守护进程的区别:

这是一个普通的通过继承Process类的方式创建子进程的方式,从结果来看不出意料,整个进程等到所有的父进程和子进程都执行结束算是执行完毕:

下面将子进程设置为守护进程再来看看输出结果,可以看出整个进程并没有等待父进程和子进程都执行完,而是父进程执行结束就整个都结束了,没有等待子进程输出:

两种创建子进程的方式的比较:

继承类是以面向对象考虑这个事的,所以业务逻辑复杂,建议使用继承类,更好理解。

3、进程池

当需要创建的子进程数量不多时, 可以直接利用multiprocessing中的Process动态生成多个进程, 但如果是上百甚至成千上万的目标, 这种方法就显得十分不方便, 此时就可以用到multiprocessing模块提供的Pool方法。

初始化Pool时, 可以指定一个最大进程数, 当有新的请求提交到Pool中时,如果池还没有满, 那么就会创建一个新的进程来执行该请求; 但如果池中的进程数已经达到指定的最大值, 那么该请求就会等待, 直到池中有进程结束, 才会创建新的进程, 请看下面的实例:

.apply_async函数是进程池中的多个进程采用非阻塞的方式并发执行,.apply函数采用阻塞的方式执行,即进程池中只能有一个进程在执行,退出一进入一个。

4、进程间通信

Process之间有时需要通信, 操作系统提供了很多机制来实现进程间的通信。可以使用multiprocessing模块的Queue实现多进程之间的数据传递, Queue本身是一个消息列队程序, 

说明:

1、初始化Queue()对象时( 例如: q=Queue()) , 若括号中没有指定最大可接收的消息数量, 或数量为负值, 那么就代表可接受的消息数量没有上限( 直到内存的尽头) ;

2、Queue.qsize(): 返回当前队列包含的消息数量;

3、Queue.empty(): 如果队列为空, 返回True, 反之False ;

4、Queue.full(): 如果队列满了, 返回True,反之False;

5、Queue.get([block[, timeout]]): 获取队列中的一条消息, 然后将其从列队中移除, block默认值为True;Queue.get_nowait(): 相当Queue.get(False);

a) 如果block使用默认值, 且没有设置timeout( 单位秒) , 消息列队如果为空, 此时程序将被阻塞( 停在读取状态) , 直到从消息列队读到消息为止,如果设置了timeout, 则会等待timeout秒, 若还没读取到任何消息, 则抛出"Queue.Empty"异常;

b) 如果block值为False, 消息列队如果为空, 则会⽴刻抛出"Queue.Empty"异常;

6、Queue.put(item,[block[, timeout]]): 将item消息写入队列, block默认值为True;Queue.put_nowait(item): 相当Queue.put(item, False);

a) 如果block使用默认值, 且没有设置timeout( 单位秒) , 消息列队如果已经没有空间可写入, 此时程序将被阻塞( 停在写入状态) , 直到从消息列队腾出空间为止, 如果设置了timeout, 则会等待timeout秒, 若还没空间, 则抛出"Queue.Full"异常;

b) 如果block值为False, 消息列队如果没有空间可写入, 则会立刻抛出"Queue.Full"异常;

看下面在Ubuntu系统上运行的例子,可以看出读和写进程操作的是同一个消息队列:

5、进程池中的Queue

如果要使用Pool创建进程, 就需要使用multiprocessing.Manager()中的Queue(), 而不是multiprocessing.Queue(), 否则会抛异常。

在这里需要强调一下,使用apply阻塞的方式实现进程池中进程的相互通信时,理论上讲,读和写进程操作的应该是同一个消息队列,即对同一个消息队列执行读写的操作,并且这里采用阻塞的方式,最要的是为了让读进程在读的时候,写进程已经全部写入了,因为两个进程如果采用非阻塞的方式放入进程池的话,它们抢占消息队列进行操作的机会是平等且不可预估的,这样就造成了可能读进程进行读取的操作在写进程写入之前,这样如果读进程不使用死循环的话可能就无法读取全部的数据。看一下通过apply阻塞的方式实现进程池间的相互通信在Ubuntu上的执行结果(Windows)上也一样:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容

  • 一、进程的概念 相信很多同学都听说过windows、linux,MacOS都是多任务,多用户的操作系统。那什么是多...
    转身后的那一回眸阅读 926评论 0 1
  • fork方式创建进程 简单的fork 主进程fork时返回值大于0,子进程fork时返回值等于0 os.getpi...
    cHl0aG9u阅读 739评论 0 1
  • 进程的基本概念 进程是程序的一次执行,每个进程都有自己的地址空间,内存,数据栈以及其他记录其运行轨迹的辅助数据。多...
    XYZeroing阅读 1,178评论 0 13
  • 1.进程 1.1多线程的引入 现实生活中 有很多的场景中的事情是同时进行的,比如开车的时候手和脚共同来驾驶汽车,再...
    TENG书阅读 482评论 0 0
  • 多进程 什么是多任务 什么叫做多任务呢?简单的说,就是操作系统(OS)可以同时运行多个任务。如你可以一边浏览网页,...
    妄想成为正太的包蜀黍阅读 824评论 0 0