python进程

要让Python程序实现多进程(multiprocessing),我们先了解操作系统的相关知识。Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。Python的os模块封装了常见的系统调用,其中就包括fork。可以在linux系统下的Python程序中轻松创建子进程:

import os
import time

pid = os.fork()
if pid == 0:
        print('哈哈。。。')
else:
        print('嘿嘿。。。')

print('中午了。。。')

结果如下:



可以利用多进程调用函数:

import os
import time

def sing():
        for i in range(3):
                print('唱歌。。。')
                print(time.ctime())
                time.sleep(1)

def dance():
        for i in range(3):
                print('跳舞。。。')
                print(time.ctime())
                time.sleep(1)

if __name__ == '__main__':
        pid = os.fork()
        
        if pid == 0:
                sing()
        else:
                dance()

结果如下:



在前面提到了进程号,什么是进程号(PID)呢?打开我们的资源管理器:



可以使用os模块里的getpid()得到自己的进程号,使用getppid()得到父进程的进程号。
import os
pid = os.fork()
print(pid)
if pid == 0:
        print('哈哈。。。')
        print('我是子进程:%s,我的父进程:%s'%(os.getpid(),os.getppid()))
else:
        print('嘿嘿。。。')
        print('我是父进程:%s,我的父进程:%s'%(os.getpid(),os.getppid()))

结果如下:



可以清楚的看到,父进程返回的值是子进程的进程号。
当有一个全局变量时,在父进程或子进程中改变全局变量,会对其他进程有影响吗?

import os
import time

num = 10
pid = os.fork()
print(pid)
if pid == 0:
        num = num +10
        print('哈哈。。。num=%s'%num)
        print('我是子进程:%s,我的父进程:%s'%(os.getpid(),os.getppid()))
else:
        time.sleep(3)
        num = num*10
        print('嘿嘿。。。num=%s'%num)
        print('我是父进程:%s,我的父进程:%s'%(os.getpid(),os.getppid()))

结果如下:



正如之前所说,当执行fork()语句时,子进程会将父进程的代码拷贝一份,各执行各的,互不影响。
那么多个fork是如何执行的?


import os
import time

pid = os.fork()
if pid == 0:
        print('哈哈1。。。')
else:
        print('主进程嘿嘿1。。。')

pid = os.fork()
if pid == 0:
        print('哈哈2。。。')
else:
        print('主进程嘿嘿2。。。')

结果如下:



如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。由于Windows没有fork调用,难道在Windows上无法用Python编写多进程的程序?由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。

import multiprocessing
import os


def foo():
    print('子进程id:%s父进程id:%s' % (os.getpid(), os.getppid()))


if __name__ == '__main__':
    print('父进程id:%s' % os.getpid())
    p = multiprocessing.Process(target=foo)
    print('子进程将要执行。。。')
    p.start()
    print('主进程结束。。')

结果如下:



对比linux下fork()的多进程,出现了明显的区别,在第一个例子中,父进程和子进程都打印了‘中午了’。而在multiprocessing的模块下,子进程只进行自己函数里的内容,并没有打印‘主程序结束这句话’。只有主程序打印了这句话。
Process语法结构如下:



target:表示这个进程实例所调用对象;
args:表示调用对象的位置参数元组;

kwargs:表示调用对象的关键字参数字典;
name:为当前进程实例的别名;
group:大多数情况下用不到;
Process类常用方法;
is_alive():判断进程实例是否还在执行;
join([timeout]):是否等待进程实例执行结束,或等待多少秒;
start():启动进程实例(创建子进程);
run():如果没有给定target参数,对这个对象调用start()方法时,就将执行对象中的run()方法;
terminate():不管任务是否完成,立即终止;
Process类常用属性;
name:当前进程实例别名,默认为Process-N,N为从1开始递增的整数;
pid:当前进程实例的PID值;
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。代码如下:

import multiprocessing
import os


def foo():
    print('子进程id:%s父进程id:%s' % (os.getpid(), os.getppid()))


if __name__ == '__main__':
    print('父进程id:%s' % os.getpid())
    p = multiprocessing.Process(target=foo)
    print('子进程将要执行。。。')
    p.start()
    p.join()
    print('主进程结束。。')

结果如下:



传递其他的参数代码如下:

import multiprocessing
import os


def foo(name, no, **kwargs):
    print('%s子进程%s的id:%s父进程id:%s' % (no, name, os.getpid(), os.getppid()))
    print(kwargs)


if __name__ == '__main__':
    print('父进程id:%s' % os.getpid())
    p1 = multiprocessing.Process(target=foo, name='哈哈', args=('xx', 18), kwargs={'1': 'haha', '2': 'hehe'})
    p2 = multiprocessing.Process(target=foo, args=('ff', 30), kwargs={'xx': '哈哈', 'kk': '嘿嘿'})
    print('子进程将要执行。。。')
    p1.start()
    print(p1.name)
    p2.start()
    print(p2.name)
    p1.join()
    p2.join()
    print('主进程结束。。')

结果如下:



可以看出如果设置name,打印进程的名字是按设置的name来,如果不设置,系统默认的是Process-x。
有了以上的基础,就可以试着模拟多进程的下载过程。

import multiprocessing
import time
import random


def download(name):
    print('文件%s添加至下载队列。。。' % name)
    time.sleep(random.randint(2, 5))
    print('文件%s下载成功。。。' % name)


if __name__ == '__main__':
    p1 = multiprocessing.Process(target=download, name='哈哈', args=('三国_01.avi',))
    p2 = multiprocessing.Process(target=download, args=('三国_02.avi',))
    p3 = multiprocessing.Process(target=download, args=('三国_03.avi',))
    print('将要开始下载。。。')
    p1.start()
    p2.start()
    p3.start()
    p1.join()
    p2.join()
    p3.join()
    print('下载结束。。')

结果如下:



创建进程Process的子类。也就是说创建新的进程还能够使用类的方法,可以自定义一个类,继承Process类,每次实例化这个类的时候,就等同于实例化一个进程对象。

import multiprocessing
import os


class MyProcess(multiprocessing.Process):
    def __init__(self, name):
        super().__init__(name=name)

    def run(self):
        print('我是子进程:%s,我的PID是%s' % (self.name, os.getpid()))
        print('我的父进程PID是:%s' % os.getppid())


if __name__ == '__main__':
    print('程序开始。。。')
    print('程序PID是:%s' % os.getpid())
    p1 = MyProcess('进程一')
    p1.start()
    p1.join()
    print('程序结束。。。')

输出结果:


因为Process类本身也有init方法,这个子类相当于重写了这个方法。所以再次调用父类的方法,完成初始化操作。
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行。

import multiprocessing
import os, time, random


def worker(msg):
    t_start = time.time()
    print("%s开始执行,进程号为%d" % (msg, os.getpid()))
    # random.random()随机生成0~1之间的浮点数
    time.sleep(random.random() * 2)
    t_stop = time.time()
    print(msg, "执行完毕,耗时%0.2f" % (t_stop - t_start))


if __name__ == '__main__':

    po = multiprocessing.Pool(3)  # 定义一个进程池,最大进程数3
    for i in range(0, 10):
        # Pool.apply_async(要调用的目标,(传递给目标的参数元祖,))
        # 每次循环将会用空闲出来的子进程去调用目标
        po.apply_async(worker, (i,))

    print("----start----")
    po.close()  # 关闭进程池,关闭后po不再接收新的请求
    po.join()  # 等待po中所有子进程执行完成,必须放在close语句之后
    print("-----end-----")

结果如下:



multiprocessing.Pool常用函数解析:
apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表。
close():关闭Pool,使其不再接受新的任务;
terminate():不管任务是否完成,立即终止;
join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;
apply(func[, args[, kwds]]):使用阻塞方式调用func。

import multiprocessing
import os, time, random


def worker(msg):
    t_start = time.time()
    print("%s开始执行,进程号为%d" % (msg, os.getpid()))
    time.sleep(random.random() * 2)
    t_stop = time.time()
    print(msg, "执行完毕,耗时%0.2f" % (t_stop - t_start))


if __name__ == '__main__':
    #print("----start----")
    po = multiprocessing.Pool(3)
    for i in range(0, 10):
        po.apply(worker, (i,))
    print("----start----")
    po.close()
    po.join()
    print("-----end-----")

结果如下:



process之间有时需要通信,操作系统提供了很多机制来实现进程间的通信。以下使用Queue进行通信。

import multiprocessing

q = multiprocessing.Queue(3)
q.put('哈哈')
q.put('嘿嘿')
print(q.full())
q.put('呵呵')
print(q.full())
try:
    q.put('第四次', timeout=3)
except:
    print('消息队列已满,当前消息数量:%s' % q.qsize())
if not q.empty():
    for i in range(q.qsize()):
        print(q.get())

结果如下:



初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头);
Queue.qsize():返回当前队列包含的消息数量;
Queue.empty():如果队列为空,返回True,反之False ;
Queue.full():如果队列满了,返回True,反之False;
Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True;
1)如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出"Queue.Empty"异常;
2)如果block值为False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常;
Queue.get_nowait():相当Queue.get(False);
Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True;
1)如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出"Queue.Full"异常;
2)如果block值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;
Queue.put_nowait(item):相当Queue.put(item, False);
下面在主进程中创建两个子进程,一个往Queue里写入数据,一个从Queue里读数据。

import multiprocessing
import time
import random


# 写数据进程执行的代码:
def write(q):
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())


# 读数据进程执行的代码:
def read(q):
    while True:
        if not q.empty():
            value = q.get(True)
            print('Get %s from queue.' % value)
            time.sleep(random.random())
        else:
            break


if __name__ == '__main__':
    # 主(父)进程创建Queue,并传给各个子进程:
    q = multiprocessing.Queue()
    pw = multiprocessing.Process(target=write, args=(q,))
    pr = multiprocessing.Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 等待pw结束:
    pw.join()
    # 启动子进程pr,读取:
    pr.start()
    pr.join()
    print('所有数据都写入并且读完')

结果如下:



如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue(),否则会得到一条如下的错误信息:
RuntimeError: Queue objects should only be shared between processes through inheritance.
下面的实例演示了进程池中的进程如何通信

import multiprocessing
import os


def reader(q):
    print('reader启动(%s),父进程为(%s)' % (os.getpid(), os.getppid()))
    for i in range(q.qsize()):
        print('reader从Queue获取到消息:%s' % q.get(True))


def writer(q):
    print('writer启动(%s),父进程为(%s)' % (os.getpid(), os.getppid()))
    for i in 'ABCD':
        q.put(i)


if __name__ == '__main__':
    print('(%s) start' % os.getpid())
    q = multiprocessing.Manager().Queue()  # 使用Manager中的Queue来初始化
    po = multiprocessing.Pool()
    # 使用阻塞模式创建进程,这样就不需要在reader中使用死循环了,可以让writer完全执行完成后,再用reader去读取
    po.apply_async(writer, (q,))
    po.apply_async(reader, (q,))
    po.close()
    po.join()
    print('(%s) End' % os.getpid())

结果如下:



如果使用非阻塞式
po.apply_async(writer, (q,))po.apply_async(reader, (q,))
可能出现如下结果:



因为cpu的分配不一定谁先谁后,所以可能发生先进行读的进程,才开始写的进程。因此出现了上图的结果。
小结

在Unix/Linux下,可以使用fork()调用实现多进程。
要实现跨平台的多进程,可以使用multiprocessing模块。
进程间通信是通过Queue实现的。

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

推荐阅读更多精彩内容

  • 一多任务的引入 有很多的场景中的事情是同时进行的,比如开车的时候手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的...
    五行缺觉阅读 638评论 0 0
  • 转自:http://www.liaoxuefeng.com/wiki/0014316089557264a6b348...
    放风筝的小小马阅读 220评论 0 2
  • 1.进程 1.1多线程的引入 现实生活中 有很多的场景中的事情是同时进行的,比如开车的时候手和脚共同来驾驶汽车,再...
    TENG书阅读 482评论 0 0
  • 一、进程的概念 相信很多同学都听说过windows、linux,MacOS都是多任务,多用户的操作系统。那什么是多...
    转身后的那一回眸阅读 926评论 0 1
  • 1.1.1多任务的引入 什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器...
    PythonMaO阅读 447评论 0 1