Python中的多进程与多线程(二)

Python中的多进程与多线程(二)

Python实现多进程

一、背景了解

  • Unix/Linux操作系统提供了一个fork()系统调用,它较为特殊。普通的函数调用,调用一次,返回一次;但是fork()调用一次,返回两次,这是因为操作系统自动把当前进程(成为父进程)复制了一份(成为子进程),然后分别在父进程和子进程中返回

  • 子进程的返回值都为0,而父进程的返回值为子进程的ID。这是由于一个父进程可以fork出很多子进程,所以父进程要记录每个子进程的ID,而子进程只需要调用getppid()就能拿到父进程的ID

  • Python的os模块封装了常见的系统调用,其中就包括fork,因此可以使用Python方便地创建子进程:

import os

print("Process %s start..." % os.getpid())

pid = os.fork()

if pid == 0:
    print("I am a child process %s, i am forked by process % s..." % (os.getpid(), os.getppid()))

else:
    print("I am process %s, i fork a process %s..." % (os.getpid(), pid))
  • 运行结果如下:
Process 1174 start...
I am process 1174, i fork a process 1175...
I am a child process 1175, i am forked by process 1174...
  • 但是Windows没有fork调用,上面的代码在Windows系统上无法运行。这时候Python就派上了用场,它提供了一个跨平台的多进程模块:multiprocessing,通过这个模块,可以很方便地编写多进程程序,而且在不同的平台(Unix、Linux、Windows)都可以执行

二、Python多进程编程

1、multiprocessing

  • multiprocessing模块提供了一个Process类来代表一个进程对象,下面演示如何启动一个子进程并等待其结束
import os
from multiprocessing import Process

def my_proc(arg):
    print("Run the child process %s (%s)..." % (arg, os.getpid()))

if __name__ == "__main__":
    print("Parent process is %s" % os.getpid())

    p = Process(target=my_proc, args=("first", ))

    print("Child process will start...")
    p.start()
    p.join()
    print("Child process end...")
  • 运行结果如下:
Parent process is 1520
Child process will start...
Run the child process first (1521)...
Child process end...
  • 创建子进程时,传入一个执行函数和函数的参数,以此创建一个Process对象,然后调用start()方法启动。其中,join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步(待深究~)

2、Pool

  • 在某些情况下,我们希望批量创建多个子进程,或者给定子进程数的上限,避免消耗太多的系统资源。而通过Pool(进程池)的方式,就能完成这项工作:
import os, time
from multiprocessing import Pool

def child_process(name, sleep_time):
    print('Child process %s with processID %s starts...' % (name, os.getpid()))
    time.sleep(sleep_time)
    print('Child process %s with processID %s ends...' % (name, os.getpid()))

if __name__ == "__main__":
    print("Parent processID: %s" % (os.getpid()))

    p = Pool() #进程池的默认大小是CPU的数量
    # p = Pool(10) #可以通过参数的形式指定进程池中子进程的数量

    #这里因为我的电脑为4核,所以创建5个子进程
    for i in range(5):
        #通过apply_async()方法向进程池提交目标请求
        p.apply_async(child_process, args=(str(i), i+1, ))

    print("Child processes are running...")

    p.close()
    p.join()
    print("All processes end.")
  • 运行结果:
Parent processID: 1082
Child processes are running...
Child process 1 with processID 1084 starts...
Child process 0 with processID 1083 starts...
Child process 2 with processID 1085 starts...
Child process 3 with processID 1086 starts...
Child process 0 with processID 1083 ends...
Child process 4 with processID 1083 starts...
Child process 1 with processID 1084 ends...
Child process 2 with processID 1085 ends...
Child process 3 with processID 1086 ends...
Child process 4 with processID 1083 ends...
All processes end.
  • 其中需要注意以下几点:

    • join()方法用来等待进程池中的所有子进程结束再向下执行代码,而且必须在p.close()或者p.terminate()之后执行

      • close():关闭进程池,使之不能再添加新的进程。已经执行的进程会等待继续执行直到结束

      • terminate():强制终止进程池,正在执行的进程也会被强制终止

    • 注意观察运行结果你会发现,process0、1、2、3是立即执行的,但process4要等到它们都执行结束后才开始执行,这是因为我的电脑cpu_count() == 4,因此最多同时执行4个进程。所以如果你的电脑是八核的,则至少设置为Pool(9)才能看到这样的效果

3、进程间通信

  • 进程之间是肯定需要通信的,操作系统提供了多种机制来实现进程间的通信。Python的multiprocess模块包装了底层的机制,提供了Queue、Pipe等多种方式供进程间的通信

3.1、Queue、Lock

  • Queue是multiprocess提供的一个模块,它的数据结构就是FIFO(先进先出)的队列,其中常用的方法有:put(object)入队、get()出队、empty()判断队列是否为空

  • Lock:当多个子进程对同一个Queue执行写操作时,为了避免并发操作产生冲突,可以通过加锁的方式使得某个子进程对该Queue拥有唯一的写权限,其它子进程必须等待该锁释放后才能再开始执行写操作

  • 下面给出一个实例,使用Queue实现进程间的通信:

import os, time, random
from multiprocessing import Process, Queue, Lock

#写数据进程
def write(q, lock, name):
    print("Child process %s starts..." % name)
    #获得锁
    lock.acquire()
    for val in ['A', 'B', 'C']:
        print("Put %s to queue..." % val)
        q.put(val)
    #释放锁
    lock.release()

    print("Child process %s ends..." % name)

#读数据进程
def read(q, name):
    print("Child process %s starts..." % name)

    while True:
        val = q.get()
        print("Get %s from queue.." % val)

if __name__ == "__main__":
    q = Queue()
    lock = Lock()

    pw = Process(target=write, args=(q, lock, "write", ))
    pr = Process(target=read, args=(q, "read",))

    pw.start()
    pr.start()

    pw.join()
    pr.terminate()

    print("Finish.")
  • 运行结果:
Child process write starts...
Put A to queue...
Child process read starts...
Put B to queue...
Get A from queue..
Put C to queue...
Get B from queue..
Child process write ends...
Get C from queue..
Finish.

3.2、Pipe

  • Pipe是另一种进程间通信的方式,俗称“管道”。它由两端组成,一端往管道里写入数据,另一端从管道里读取数据

  • 下面给出使用Pipe进行通信的实例:

import os, time, random
from multiprocessing import Process, Pipe

#发送数据进程
def send(child_pipe, name):
    print("Child process %s starts.." % name)

    child_pipe.send("this is jiangnanmax.")
    child_pipe.close()
    time.sleep(random.random())

    print("Child process %s ends." % name)

#接收数据进程
def receive(parent_pipe, name):
    print("Child process %s starts.." % name)

    print(parent_pipe.recv())
    time.sleep(random.random())

    print("Child process %s ends." % name)

if __name__ == "__main__":
    # 创建管道
    parent, child = Pipe()
    # 创建send进程
    ps = Process(target=send, args=(child, 'SEND'))
    # 创建receive进
    pr = Process(target=receive, args=(parent, 'RECEIVE'))
    # 启动send进程
    ps.start()
    # 等待send进程结束
    ps.join()
    # 启动receive进程
    pr.start()
    # 等待receive进程结束
    pr.join()

    print("Finish.")
  • 运行结果:
Child process SEND starts..
Child process SEND ends.
Child process RECEIVE starts..
this is jiangnanmax.
Child process RECEIVE ends.
Finish.

猜你喜欢

转载自blog.csdn.net/J__Max/article/details/82901873