目录
前言
一、进程的使用
(一)简介
(二)什么是进程
(三)多进程
(四)常用功能
(五)应用场景
(六)总结
二、多进程通信
(一)Queue队列
(二)Pipe管道
(三)Value和Array
(四)同步源语
(五)共享内存和命名空间
(六)总结
三、进程池
(一)线程池的基本概念
(二)主要方法
(三)线程池的优势
(四)使用场景
四、进程的生产者-消费者模式
(一)实现步骤
(二)示例
(三)特点和优势
(四)总结
五、进程和线程的对比
(一)定义
(二)内存空间
(三)创建和开销
(四)并发和并行
(五)全局解释器锁(GIL)
(六)适用场景
(七)进程间与线程间通信
(八)稳定性与安全性
(九)代码示例
(十)总结
六、总结
前言
上篇文章主要了解python的线程,如何创建线程,如何通过线程实现生产者-消费者模式以及线程池的使用等等,接下来这篇文章讲解python的进程,有问题欢迎一起探讨。
一、进程的使用
(一)简介
在 python 中,进程是操作系统分配的独立执行单元,它包含程序代码、数据以及一系列运行时资源,如内存、文件描述符等。进程是操作系统进行任务调度和资源管理的基本单位。python 提供了多种方法来创建和管理进程,通常通过 multiprocessing
模块来实现多进程操作。
(二)什么是进程
进程是操作系统中正在执行的程序实例。每个进程都有自己的内存空间、全局变量以及系统资源。进程之间是独立的,一个进程的崩溃不会影响其他进程。
特点:
-
独立内存空间:每个进程都有独立的内存空间,因此不同进程之间的数据互不干扰。
-
并行运行:多个进程可以在多核 CPU 上真正并行运行。
-
系统资源隔离:操作系统为每个进程分配资源,如 CPU、内存等)
(三)多进程
由于 python 的全局解释器锁:GIL,多线程在 python 中无法实现真正的并行,尤其是 CPU 密集型任务。因此,对于这种任务,通常会使用多进程方式代替多线程。python 提供了 multiprocessing
模块来轻松创建和管理多个进程。
multiprocessing
模块
该模块允许通过创建子进程的方式并行地执行任务。这个模块提供了与 threading
模块类似的接口,但进程之间的内存是独立的,需要使用进程间通信IPC来进行数据交换。
示例:
import multiprocessing
import osdef worker():print(f"Worker Process ID: {os.getpid()}")if __name__ == "__main__":# 创建子进程process = multiprocessing.Process(target=worker)process.start()process.join() # 等待子进程完成
在该例子中,worker
函数在子进程中运行,multiprocessing.Process
用来创建和管理子进程。
(四)常用功能
进程常用功能主要有以下几种:
-
Process
类:用于创建一个新进程,执行指定的目标函数。 -
Pool
类:用于管理进程池,以便在多个进程之间并行执行任务。 -
进程间通信:通过
Queue
、Pipe
、Value
、Array
等方式实现进程间的数据共享和通信。 -
同步原语:如
Lock
、Event
、Semaphore
等,用于确保进程之间的同步,避免资源竞争问题。
(五)应用场景
多进程常用于以下场景:
-
CPU 密集型任务:如图像处理、机器学习模型训练等,此类任务通常需要大量的计算资源,通过多进程可以充分利用多核 CPU。
-
任务隔离:当你希望多个任务彼此独立运行,且互不干扰时,可以使用多进程。
(六)总结
进程是操作系统管理资源的基本单位,每个进程都有独立的内存空间和资源。multiprocessing
模块提供了易于使用的接口,方便创建和管理多进程。进程间通信可以通过队列、管道等方式来实现,但进程之间的资源是独立的。多进程尤其适用于 CPU 密集型任务,能够提升性能。
二、多进程通信
多进程通信是指不同进程之间交换数据和信息的方式。由于进程有独立的内存空间,它们之间的数据不能直接共享,因此需要使用特定的通信机制来进行数据交换。python 的 multiprocessing
模块提供了多种进程间通信的方式,通过五个方面介绍多进程通信的机制;
(一)Queue队列
Queue
是一种线程安全的队列,用于在进程之间传递数据。它支持先进先出操作,适合用于进程间的消息传递和任务分配。
特点:
-
线程安全:
Queue
实现了线程安全,因此可以被多个进程同时访问而不需要额外的锁。 -
支持多种操作:提供
put()
、get()
、empty()
等方法进行数据操作。
示例:
from multiprocessing import Process, Queuedef worker(q):q.put("Hello from worker")if __name__ == "__main__":q = Queue()p = Process(target=worker, args=(q,))p.start()print(q.get()) # 从队列中获取数据p.join()
(二)Pipe管道
Pipe
提供了一对连接的管道,允许两个进程进行双向通信。每个管道有两个端点,一个用于发送数据,另一个用于接收数据。
特点:
-
双向通信:可以通过
Pipe
进行双向数据传输,但需要在两个进程中分别使用conn1.send()
和conn2.recv()
进行通信。 -
效率高:比
Queue
更轻量,适合用于简单的进程间通信。
示例:
from multiprocessing import Process, Pipedef worker(conn):conn.send("Hello from worker")conn.close()if __name__ == "__main__":parent_conn, child_conn = Pipe()p = Process(target=worker, args=(child_conn,))p.start()print(parent_conn.recv()) # 从管道中接收数据p.join()
(三)Value和Array
Value
和 Array
是用于在进程之间共享简单数据类型,如整数、浮点数和数组的机制。这些对象被存储在共享内存中,因此可以被多个进程直接访问。
特点:
-
共享内存:允许不同进程访问同一块内存区域,从而实现数据共享。
-
需要同步:由于进程可以同时修改这些共享数据,可能会发生数据竞争,因此通常需要使用锁
Lock
来进行同步。
示例:
from multiprocessing import Process, Value, Array, Lockdef worker(val, arr, lock):with lock:val.value += 1for i in range(len(arr)):arr[i] += 1if __name__ == "__main__":lock = Lock()val = Value('i', 0)arr = Array('i', range(10))p = Process(target=worker, args=(val, arr, lock))p.start()p.join()print(val.value) # 打印共享的整数值print(arr[:]) # 打印共享的数组
(四)同步源语
Lock:用于进程间的互斥访问,防止多个进程同时访问共享资源而导致数据竞争。
Event:用于进程间的同步,通过设置和清除事件来协调进程之间的活动。
Semaphore:用于控制对共享资源的访问,允许一定数量的进程同时访问资源。
Condition:提供更复杂的同步机制,允许进程在某些条件满足时进行通信和同步。
示例:
from multiprocessing import Process, Lock, Valuedef worker(val, lock):with lock:val.value += 1if __name__ == "__main__":lock = Lock()val = Value('i', 0)processes = [Process(target=worker, args=(val, lock)) for _ in range(10)]for p in processes:p.start()for p in processes:p.join()print(val.value) # 打印最终的共享整数值
(五)共享内存和命名空间
对于更复杂的数据共享场景,可以使用 multiprocessing.Manager
提供的 Manager
对象来创建和管理共享的命名空间和数据结构,如字典、列表等。
示例:
from multiprocessing import Process, Managerdef worker(shared_dict):shared_dict['key'] = 'value'if __name__ == "__main__":manager = Manager()shared_dict = manager.dict()p = Process(target=worker, args=(shared_dict,))p.start()p.join()print(shared_dict) # 打印共享的字典内容
(六)总结
-
Queue:适用于消息传递和任务分配,线程安全,支持多进程使用。
-
Pipe:适用于简单的双向通信,效率高,但通信是点对点的。
-
Value 和 Array:适用于共享简单数据类型和数组,需配合锁使用以避免数据竞争。
-
Lock、Event、Semaphore、Condition:用于进程同步和资源管理。
-
Manager:提供了更多复杂的数据共享和同步机制。
三、进程池
python的进程池multiprocessing.Pool
是 multiprocessing
模块提供的一种高级并行处理方式,用于同时执行多个进程,从而充分利用多核CPU资源。在许多需要并行执行任务的场景下,进程池可以大大简化并行代码的编写,并提高程序的运行效率。
(一)线程池的基本概念
进程池提供了一种机制,可以一次性创建多个进程并将其放入池中,方便在需要时复用这些进程。这样避免了频繁创建和销毁进程的开销。在处理大量任务时,将任务分配给多个进程池中的进程并行处理,可以显著加速任务执行。
(二)主要方法
(1)Pool()
: 创建进程池对象。
Pool([processes])
:processes
是可选参数,指定进程池中并行执行的进程数量。如果不指定,默认使用系统的CPU核心数量。
(2)apply()
: 同步地将任务提交给进程池中的某个进程执行,直到任务完成后才返回结果。
apply(func, args=())
:执行一个函数,并传递参数。该方法会阻塞主进程,直到任务完成并返回结果。
(3)apply_async()
: 异步地将任务提交给进程池中的某个进程执行,不会阻塞主进程。
apply_async(func, args=(), callback=None)
:异步调用某个函数,可以通过callback
参数获取任务完成后的结果。
(4)map()
: 同步地将可迭代对象的每个元素分配给进程池中的进程执行指定函数,类似于 map()
函数,但支持并行。
map(func, iterable)
:阻塞主进程,直到所有任务完成后返回结果。
(5)map_async()
: 异步地将可迭代对象的每个元素分配给进程池中的进程执行指定函数,不会阻塞主进程。
map_async(func, iterable)
:和map()
类似,但不阻塞主进程。
(6)close()
: 关闭进程池,防止新的任务提交。已经提交的任务会继续执行,直到完成。
(7)join()
: 等待所有进程池中的进程执行完毕,通常在调用 close()
之后使用。
(8)terminate()
: 强行终止所有正在执行的进程,不等待任务完成。
示例:
import multiprocessing
import timedef worker_function(x):return x * xif __name__ == "__main__":# 创建一个进程池,指定最大并发进程数为4pool = multiprocessing.Pool(processes=4)# 使用map方法同步执行任务results = pool.map(worker_function, range(10))print("同步执行结果:", results)# 异步执行任务async_result = pool.apply_async(worker_function, args=(5,))print("异步执行结果:", async_result.get()) # 获取异步执行的结果# 关闭进程池并等待所有进程结束pool.close()pool.join()
(三)线程池的优势
-
简化并行处理:使用进程池可以避免手动管理进程的启动、执行和结束,大大简化代码复杂度。
-
复用进程:进程池会复用已创建的进程,减少频繁创建销毁进程的开销。
-
高效任务调度:进程池可以高效地调度任务,并根据可用的CPU核心数合理分配任务,提高性能。
(四)使用场景
-
需要并行处理多个相同或相似的任务,比如处理多个文件、并发爬虫等。
-
CPU密集型任务,例如大规模计算任务、多线程无法充分利用多核时。
四、进程的生产者-消费者模式
生产者-消费者模式是一种常见的并发设计模式,用于解决不同生产者线程/进程和消费者线程/进程之间的协作问题。其核心思想是通过某种中间数据结构来解耦生产者和消费者,使它们之间的执行流程独立。生产者产生数据,消费者消耗数据,而队列负责在两者之间传递数据。
在生产者-消费者模式中:
- 生产者:生成数据或任务,将其放入队列。
- 消费者:从队列中取出数据或任务进行处理。
- 队列:起到缓冲作用,生产者和消费者都可以并发执行,彼此互不干扰。
python标准库中的 multiprocessing
模块提供了 Queue
对象,用来在多个进程之间传递数据,非常适合实现生产者-消费者模式。queue.Queue
对象用于多线程场景,而 multiprocessing.Queue
适用于多进程场景。
(一)实现步骤
-
生产者进程:负责生产数据,并将数据放入队列。
-
消费者进程:负责从队列中取出数据并进行处理。
-
队列:作为中间的共享数据结构,用于存储生产者产生的数据,供消费者取用。
-
同步机制:通过
Queue
提供的同步机制,自动处理生产者和消费者之间的资源争用问题,避免死锁或数据竞争。
(二)示例
以下示例展示了如何使用 multiprocessing
模块中的 Process
和 Queue
实现生产者-消费者模式
import multiprocessing
import time
import random# 生产者函数
def producer(queue, items):for item in items:print(f"生产者正在生产: {item}")queue.put(item) # 将生产的数据放入队列time.sleep(random.uniform(0.1, 0.5)) # 模拟生产时间queue.put(None) # 用于结束信号,通知消费者结束# 消费者函数
def consumer(queue):while True:item = queue.get() # 从队列中取数据if item is None: # 如果取到的是结束信号,退出循环breakprint(f"消费者正在消费: {item}")time.sleep(random.uniform(0.1, 0.5)) # 模拟消费时间if __name__ == "__main__":# 创建一个队列,用于生产者和消费者之间的通信queue = multiprocessing.Queue()# 定义要生产的项目列表items_to_produce = ['item1', 'item2', 'item3', 'item4', 'item5']# 创建生产者进程producer_process = multiprocessing.Process(target=producer, args=(queue, items_to_produce))# 创建消费者进程consumer_process = multiprocessing.Process(target=consumer, args=(queue,))# 启动生产者和消费者进程producer_process.start()consumer_process.start()# 等待生产者进程结束producer_process.join()# 等待消费者进程结束consumer_process.join()print("生产与消费过程结束。")
代码运行逻辑:
-
生产者:生产者进程生成若干个数据(
item1
到item5
),并将其逐个放入共享队列中。生产完毕后,生产者放入一个None
作为结束信号,告诉消费者任务已经完成。 -
消费者:消费者进程不断从队列中获取数据,直到收到
None
结束信号为止。每次从队列中取出数据后进行处理。 -
队列的作用:
multiprocessing.Queue
在生产者和消费者之间共享数据,确保数据能够安全地在多个进程之间传递。它内部会处理进程间的锁和同步问题,避免数据竞争。
(三)特点和优势
-
解耦:生产者和消费者通过队列进行通信,互相独立运行。生产者只关心数据的生产,消费者只关心数据的消费,二者无需直接通信。
-
高效并发:生产者和消费者可以同时运行,避免了一方必须等待另一方完成。即使生产者产生数据的速度与消费者处理数据的速度不一致,队列的缓冲作用可以保证数据的稳定传递。
-
灵活性:可以轻松增加多个生产者或消费者,甚至可以控制生产和消费的速率,通过修改队列的大小来实现不同的并发场景。
(四)总结
生产者-消费者模式在Python中是一种非常实用的并发编程模式,适用于多线程或多进程的任务处理。通过队列来管理生产者和消费者之间的数据流动,可以实现任务的解耦和高效并发。这种模式广泛应用于各种需要异步任务处理的场景中,如消息处理系统、日志记录系统和大规模并发数据处理系统。
五、进程和线程的对比
Python 的进程(Process)和线程(Thread)是两种用于并发执行任务的机制,它们各有不同的特性和适用场景。下面从多个方面详细对比 Python 中的进程和线程。
(一)定义
-
进程(Process):进程是操作系统中资源分配和任务调度的基本单位。每个进程有自己独立的内存空间和系统资源,进程之间相互独立。
-
线程(Thread):线程是进程中的一个执行单元,多个线程共享同一进程的资源(如内存空间和全局变量),但它们可以独立执行。
(二)内存空间
-
进程:每个进程拥有独立的内存空间和资源,例如文件描述符、变量等,进程间的数据默认不能共享。因此,进程间通信IPC需要借助队列Queue、管道Pipe等机制。
-
线程:线程是同一进程中的多个执行流,线程之间共享进程的内存空间和资源,如全局变量、堆内存等,因此线程之间的数据共享更加方便,但容易出现竞争条件和资源争夺。
(三)创建和开销
-
进程:创建进程的开销较大。因为操作系统需要为每个新进程分配独立的内存和资源,进程的启动、切换和终止也涉及更多的系统调用,消耗较多的 CPU 和内存。
-
线程:线程的创建和销毁比进程要轻量很多,多个线程共享同一进程的资源,不需要重新分配内存。线程的上下文切换也比进程快。
(四)并发和并行
-
进程:进程之间是真正的并行执行。操作系统可以调度多个进程在多核 CPU 上并行运行,特别适合 CPU 密集型任务。
-
线程:python 的多线程受到全局解释器锁(GIL, Global Interpreter Lock)的限制,导致 python 的线程不能真正并行执行。这意味着,在 CPU 密集型任务中,python 多线程无法充分利用多核 CPU 的性能,但在 I/O 密集型任务(如文件读写、网络操作等)中,多线程仍然能提高效率。
(五)全局解释器锁(GIL)
-
进程:每个进程都有自己的独立 Python 解释器实例,GIL 不会影响不同进程间的并行执行。因此,Python 的多进程可以真正地并行,充分利用多核 CPU。
-
线程:由于 GIL 的存在,Python 中的多个线程在同一时刻只有一个线程能够执行 Python 字节码。因此,Python 的多线程不能在 CPU 密集型任务中实现真正的并行。
(六)适用场景
进程
-
适用于 CPU 密集型任务,比如数据处理、大量数学计算、图像处理、机器学习模型训练等。
-
适用于任务隔离,防止任务之间的干扰(例如某个进程崩溃不会影响其他进程)。
-
适用于真正的并行计算,尤其是在多核 CPU 上执行计算密集型任务时。
线程
-
适用于 I/O 密集型任务,比如文件读写、网络请求、数据库操作等。这些任务在执行时会涉及大量等待时间,线程可以通过利用这些等待时间来处理其他任务,从而提高效率。
-
适用于任务之间需要共享数据的场景,因为线程共享内存,可以避免进程间通信的开销。
-
适用于任务轻量且需要快速切换的情况。
(七)进程间与线程间通信
-
进程:由于进程之间内存隔离,进程间通信需要通过 IPC 机制,如队列、管道、共享内存等。这些方式比线程间的数据共享更复杂,且有额外的开销。
-
线程:线程之间共享进程的全局内存,因此通信更加直接和简单。但由于多个线程可能同时访问同一块内存,容易出现数据竞争问题,因此需要使用线程同步机制,如锁、条件变量等来保护共享数据。
(八)稳定性与安全性
-
进程:由于进程是完全独立的执行单元,一个进程的崩溃不会影响其他进程的执行。因此,进程更加安全、稳定。
-
线程:线程共享同一进程的内存和资源,因此一个线程的崩溃可能会导致整个进程崩溃。
(九)代码示例
进程:
from multiprocessing import Processdef worker():print("Worker process")if __name__ == "__main__":p = Process(target=worker)p.start()p.join()
线程:
import threadingdef worker():print("Worker thread")if __name__ == "__main__":t = threading.Thread(target=worker)t.start()t.join()
(十)总结
-
进程 适用于 CPU 密集型任务、需要并行处理的情况,或者需要任务隔离和稳定性时使用。由于 GIL 的限制,Python 的多线程在 CPU 密集型任务中效率不高,但多进程可以绕过这一限制。
-
线程 适用于 I/O 密集型任务,或者多个任务需要共享内存数据时使用。尽管多线程在 Python 中受到 GIL 的限制,不能实现真正的并行,但在 I/O 操作中,线程仍然有显著的优势。
六、总结
这篇文章讲的是python的进程,包括进程间通信、进程池、进程的生产者消费者模式以及进程和线程的对比,进程间的资源是互相隔离的,需要使用Queue等对象才能进行通信,进程适用于运行CPU 密集型任务以及任务型隔离,下篇文章接着讲python的协程,可以期待一下!