【Python】Python多进程详解

1. 什么进程?

进程(Process),顾名思义,就是进行中的程序。有一句话说得好:程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体。进程是资源分配的最小单元,也就是说每个进程都有其单独的内存空间。

2. 如何创建一个进程?

Unix/Linux系统通过fork系统调用创建一个进程,但是在Windows中并没有fork调用。但是别担心,Python中内置的multiprocessing模块是跨平台的,我们可以通过对multiprocess模块中的Process类进行实例化创建一个进程对象,如:

import os
from multiprocessing import Process

def run_a_sub_proc(name):
    print(f'子进程:{name}({os.getpid()})开始...')

if __name__ == '__main__':
    print(f'主进程({os.getpid()})开始...')
    # 通过对Process类进行实例化创建一个子进程
    p = Process(target=run_a_sub_proc, args=('测试进程', ))
    p.start()
    p.join()

执行结果如下:
在这里插入图片描述
创建一个子进程

这里需要明确以下主进程和子进程。当我们通过python demo.py开始执行demo.py这个程序时,程序被赋予了声明,成为一个进程,这个进程是主进程。而在主进程执行过程,通过对Process类进行实例化创建的是子进程。

3. multiprocessing基本功能

3.1 进程启动

当通过对Process类实例化获得一个进程p以后,直接通过p.start()就可以启动该进程了。可是,在start()方法的背后,实际上有三种启动方法:

  • spawn:子进程仅继承有限的资源,适用于Unix/Linux和Windows
  • fork:子进程会继承父进程中所有的资源,仅适用于Unix/Linux
  • forkserver:创建一个单进程的服务进程,专门用来处理子进程的创建,仅适用于Unix/Linux[1]
    在这里插入图片描述
    在这里插入图片描述
    目前,对于Unix/Linux,默认的启动方法是fork;而对于Windows和MacOS系统,默认的启动方法是spawn。

3.2 join()方法

在多线程中,join()方法会使主线程进入阻塞,直到调用join()方法的子线程执行完毕。那么在多进程中,join()方法的用法是一样,即使主进程进入阻塞,直到调用join()方法的子进程执行完毕。猜猜以下两个例子的运行结果会有什么不同?

# 例一
import os, time
from multiprocessing import Process

def run_a_sub_proc(name):
    print(f'子进程:{name}({os.getpid()})开始...')
    for i in range(3):
        print(f'子进程:{name}({os.getpid()})运行中...')
        time.sleep(1)

if __name__ == '__main__':
    print(f'主进程({os.getpid()})开始...')
    p1 = Process(target=run_a_sub_proc, args=('进程-1', ))
    p2 = Process(target=run_a_sub_proc, args=('进程-2', ))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
# 例二
import os, time
from multiprocessing import Process

def run_a_sub_proc(name):
    print(f'子进程:{name}({os.getpid()})开始...')
    for i in range(3):
        print(f'子进程:{name}({os.getpid()})运行中...')
        time.sleep(1)

if __name__ == '__main__':
    print(f'主进程({os.getpid()})开始...')
    p1 = Process(target=run_a_sub_proc, args=('进程-1', ))
    p2 = Process(target=run_a_sub_proc, args=('进程-2', ))
    p1.start()
    p1.join()
    p2.start()
    p2.join()

执行结果:
在这里插入图片描述
通过join方法阻塞主进程

简而言之,join()方法就是让主进程进入阻塞状态,等对应的子进程执行完毕再执行下一行,主要用于进程同步。

3.3 Pool

如果想一次性创建多个进程,可以用Pool方法(注意Pool是一个方法,不是类),如

import os, time
from multiprocessing import Process, Pool

def run_a_sub_proc(name):
    print(f'子进程:{name}({os.getpid()})开始!')
    for i in range(2):
        print(f'子进程:{name}({os.getpid()})运行中...')
        time.sleep(1)
    print(f'子进程:{name}({os.getpid()})结束!')

if __name__ == '__main__':
    print(f'主进程({os.getpid()})开始...')
    p = Pool(3)
    for i in range(1, 5):
        p.apply_async(run_a_sub_proc, args=(f"进程-{i}",))
    p.close()
    p.join()

运行结果如下
在这里插入图片描述
进程1~3结束了进程4才开始

值得注意的是,在上述代码中,进程1~3结束了进程4才开始,这是为什么呢?这是因为在p=Pool(3)中定义了每次执行的子进程个数的限制。

Pool的默认大小是你所用的电脑CPU的核数,CPU核数可通过os.cpu_count()获得。

p.join()的意思是等Pool中所有的子进程全部执行完毕再进行下一步,在调用p.join()之前需要先调用p.close()。

4 进程间通信

现在设想你需要两个进程,一个进程(接收进程)产生数据(比如从网站上爬虫,或者从websocket接收数据等),另一个进程(转发进程)对产生的数据进行处理并转发(比如计算并处理之后上传数据库,或者发送给websocket等)。这是一个非常常见的应用场景,如何把接收进程接受的数据传递给转发进程呢?直接硬写是不行的,比如下面这个错误示范

import os, time, random
from multiprocessing import Process

data: int

def recv():
    print(f'子进程:接收进程({os.getpid()})开始!')
    while True:
        global data
        # 用产生随机数的方法模拟数据的接收
        data = random.randint(1, 100)
        print(f'子进程:接收进程接收到数据{data}!')
        sleep_time = random.randint(1, 3)
        time.sleep(sleep_time)

def send():
    print(f'子进程:转发进程({os.getpid()})开始!')
    while True:
        global data
        print(f'子进程:转发进程接收到数据{data}并开始处理、转发!')

if __name__ == '__main__':
    print(f'主进程({os.getpid()})开始...')
    p1 = Process(target=recv)
    p2 = Process(target=send)
    p1.start()
    p2.start()
    p1.join()
    p2.join()

上面这个程序毫无疑问是会报错的,即便你声明了数据data是全局变量。
在这里插入图片描述
程序报错

报错的原因是:每个子进程享有独立的内存空间,接收进程产生的数据不能马上同步到转发进程中,这也就是为什么接收线程中提示“name ‘data’ is not defined”的原因。

那如何实现进程间通信呢?multiprocessing提供了两种方法:Queue和Pipe。

4.1 Queue

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

def recv(q):
    print(f'子进程:接收进程({os.getpid()})开始!')
    while True:
        # 用产生随机数的方法模拟数据的接收
        data = random.randint(1, 100)
        print(f'子进程:接收进程接收到数据{data}!')
        q.put(data)
        sleep_time = random.randint(1, 3)
        time.sleep(sleep_time)

def send(q):
    print(f'子进程:转发进程({os.getpid()})开始!')
    while True:
        # 注意:如果q里面没有数据,get()方法就会等待,直到获得一个数据并赋值给data
        data = q.get()
        print(f'子进程:转发进程接收到数据{data}并开始处理、转发!')
        time.sleep(1)

if __name__ == '__main__':
    print(f'主进程({os.getpid()})开始...')
    q = Queue()
    p1 = Process(target=recv, args=(q,))
    p2 = Process(target=send, args=(q,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()

执行结果如下
在这里插入图片描述
通过Queue实现进程间通信

需要注意两点:

  • data=q.get()过程中,如果q中没有数据,并不是返回一个None给data,get()方法会进入等待状态,直到q中有数据为止;
  • queue是先进先出(FIFO)的。

4.2 Pipe

如果你创建了很多个子进程,那么其中任何一个子进程都可以对Queue进行存(put)和取(get)。但Pipe不一样,Pipe只提供两个端点,只允许两个子进程进行存(send)和取(recv)。也就是说,Pipe实现了两个子进程之间的通信。

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

def sub_process(name, p):
    print(f'子进程:{name}({os.getpid()})开始!')
    while True:
        data_s = random.randint(1, 100)
        p.send(data_s)
        print(f'子进程:{name}发送数据:{data_s}!')
        data_r = p.recv()
        print(f'子进程:{name}接收到数据:{data_r}!')
        time.sleep(1)

if __name__ == '__main__':
    print(f'主进程({os.getpid()})开始...')
    conn_1, conn_2 = Pipe()
    p1 = Process(target=sub_process, args=("进程-1", conn_1,))
    p2 = Process(target=sub_process, args=("进程-2", conn_2,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()

执行结果
在这里插入图片描述
通过Pipe实现进程间通信

注意:

  • Queue可以被多个进程调用,而Pipe只能被两个进程调用;
  • Queue是基于Pipe实现的,因此Pipe速度比Queue快很多[2]。

5.进程间数据共享

通常不鼓励进程间数据共享,因为可能会带来“竞争危害”、产生不可预知的结果。但如果有这方面的需要,在保证数据安全的基础上也是可以的。实现线程间数据共享主要有两种方法:Value/Array和Manager

5.1 Value/Array

import os, time, random
from multiprocessing import Process, Value, Array

def sub_process(name, v, arr):
    print(f'子进程:{name}({os.getpid()})开始!')
    while True:
        if name == "修改Value":
            v.value += 1            # 通过Value.value读取Value的数值
        else:
            num = random.randint(0, 2)
            arr[num] += 1
        print(f'子进程:{name}', v.value, arr[:])
        time.sleep(random.randint(1, 3))

if __name__ == '__main__':
    print(f'主进程({os.getpid()})开始...')
    v = Value("i", 0)               # i 指整数
    arr = Array("i", [1, 2, 3])     # i 指整数型组成的数组
    p1 = Process(target=sub_process, args=("修改Value", v, arr, ))
    p2 = Process(target=sub_process, args=("修改Array", v, arr, ))
    p1.start()
    p2.start()
    p1.join()
    p2.join()

执行结果:
在这里插入图片描述
通过两个子线程对数值和数组不断进行修改

5.2 Manager

Manager()方法会返回一个服务进程,这个进程专门用来维护进程间数据的共享。Manager提供的数据格式非常多,包括list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array等。

6. 进程同步

在前面提到,不鼓励在进程间实现数据共享,因为容易产生竞争危害。例如两个线程,分别对同一个数值不断地进行+1,循环200遍,那么理论上最终这个数值会变成400,然而事实并非如此,如

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

def sub_process(name, v):
    print(f'子进程:{name}({os.getpid()})开始!')
    for i in range(200):
        v.value += 1

if __name__ == '__main__':
    print(f'主进程({os.getpid()})开始...')
    v = Value("i", 0)               # i 指整数
    p1 = Process(target=sub_process, args=("进程-1", v,))
    p2 = Process(target=sub_process, args=("进程-2", v,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print(v.value)

执行结果如下:数值并不是400,因为发生了竞争危害。
在这里插入图片描述
最终数值并不是400

解决该问题的办法就是加进程锁。

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

def sub_process(name, v, lock):
    print(f'子进程:{name}({os.getpid()})开始!')
    for i in range(200):
        lock.acquire()
        v.value += 1
        lock.release()

if __name__ == '__main__':
    print(f'主进程({os.getpid()})开始...')
    v = Value("i", 0)               # i 指整数
    lock = Lock()
    p1 = Process(target=sub_process, args=("进程-1", v, lock))
    p2 = Process(target=sub_process, args=("进程-2", v, lock))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print(v.value)

输出结果变成了预期的400:
在这里插入图片描述

最后

在学习python中有任何困难不懂的可以微信扫描下方CSDN官方认证二维码加入python交流学习
多多交流问题,互帮互助,这里有不错的学习教程和开发工具。

python兼职资源+python全套学习资料

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
在这里插入图片描述

二、Python必备开发工具

在这里插入图片描述

四、Python视频合集

观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
在这里插入图片描述

五、实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。在这里插入图片描述

六、Python练习题

检查学习结果。
在这里插入图片描述

七、面试资料

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
在这里插入图片描述
在这里插入图片描述
最后,千万别辜负自己当时开始的一腔热血,一起变强大变优秀。

  • 21
    点赞
  • 117
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Python 中有多种方法可以实现多进程,包括使用 `os.fork()`、`multiprocessing` 模块和 `concurrent.futures` 模块等。 1. 使用 `os.fork()` `os.fork()` 是一个 Unix 系统调用,它可以复制当前进程,创建一个新进程。父进程和子进程会共享代码段,但是各自有独立的数据段和堆栈段。在 Python 中,可以通过 `os.fork()` 实现多进程,示例代码如下: ```python import os pid = os.fork() if pid == 0: # 子进程 print('I am child process, my pid is', os.getpid()) else: # 父进程 print('I am parent process, my pid is', os.getpid(), 'and my child pid is', pid) ``` 2. 使用 `multiprocessing` 模块 `multiprocessing` 模块是 Python 内置的多进程模块,可以很方便地实现多进程。示例代码如下: ```python import multiprocessing def worker(): print('I am child process, my pid is', os.getpid()) if __name__ == '__main__': # 创建子进程 p = multiprocessing.Process(target=worker) # 启动子进程 p.start() # 等待子进程结束 p.join() # 父进程 print('I am parent process, my pid is', os.getpid()) ``` 3. 使用 `concurrent.futures` 模块 `concurrent.futures` 模块是 Python 内置的线程池和进程池模块,可以很方便地实现异步任务。示例代码如下: ```python import concurrent.futures def worker(): print('I am child process, my pid is', os.getpid()) if __name__ == '__main__': # 创建进程池 with concurrent.futures.ProcessPoolExecutor() as executor: # 提交异步任务 future = executor.submit(worker) # 等待任务完成 result = future.result() # 父进程 print('I am parent process, my pid is', os.getpid()) ``` 以上是三种常用的 Python进程实现方式,具体选用哪种方式,需要根据实际情况来决定。需要注意的是,在 Windows 平台上,由于 `os.fork()` 不被支持,因此不能使用第一种方式创建多进程。 ### 回答2: Python进程是指利用Python编程语言提供的多进程模块来实现并行计算。Python的多进程模块主要包括了`multiprocessing`和`concurrent.futures`两个子模块,用于实现多进程的创建、启动和管理。 使用Python的多进程可以带来多个优点。首先,使用多进程可以充分利用多核处理器的计算资源,提高程序的运行效率。其次,多进程可以实现程序的并行计算,将计算任务分配到多个进程中同时执行,从而缩短程序的运行时间。同时,多进程还可以解决Python的全局解释锁(GIL)问题,使得并行计算更加高效。 在Python中,使用多进程可以通过以下几个步骤实现。首先,需要导入`multiprocessing`或`concurrent.futures`模块。然后,通过创建`Process`或`Executor`对象来创建进程。接下来,可以使用`start()`方法启动进程,使用`join()`方法等待进程结束。同时,还可以使用`Process`或`Executor`对象提供的其他方法来管理进程的状态和结果。 除了基本的多进程操作外,Python的多进程模块还提供了一些辅助功能,如进程间通信、进程池等。进程间通信可以通过`Queue`、`Pipe`等来实现,用于实现进程之间的数据交换。而进程池可以通过`Pool`来实现,用于管理多个进程的执行和返回结果。 总之,Python进程是一种并行计算的方法,可以充分利用计算资源和提高程序的运行效率。通过多进程的方式,可以实现更快速和有效的数据处理和计算。 ### 回答3: Python进程是指在一个 Python 程序中同时运行多个进程的能力。多进程可以充分利用多核处理器,并且能够提高程序的运行效率和性能。 在 Python 中,我们可以使用 `multiprocessing` 模块来实现多进程。通过创建多个进程,每个进程都拥有独立的内存空间和数据栈,可以同时运行不同的任务。 使用 `multiprocessing` 模块,我们可以创建一个进程池,通过调用其 `Pool` 类的 `map()` 或 `apply()` 方法,将任务分配给进程池中的不同进程来并行执行。这样,我们可以有效地利用多个 CPU 核心,提高程序的运行效率。 需要注意的是,每个进程都有自己独立的内存空间,因此在多进程编程时,需要特别注意进程间的数据共享和同步问题。在多进程编程中,常用的同步机制包括锁、信号量、事件等,可以通过 `multiprocessing` 模块中的相关类来实现。 此外,多进程编程还可以进一步拓展为分布式进程编程,即将进程分布在多台机器上运行,通过网络进行通信和协调。在 Python 中,可以使用 `multiprocessing` 模块中的 `Manager` 类来实现分布式进程。 总的来说,Python 的多进程编程能够提高程序的执行效率和性能,特别适合于那些需要大量计算的任务,如数据处理、图像处理等。通过合理地运用多进程,并结合适当的同步机制,我们可以充分发挥多核处理器的潜力,提高程序的运行效率和吞吐量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值