python使用多进程multiprocessing
python使用多进程multiprocessing
- 1 多进程解释
- 2 进程的演示
- 3 进程池方法
- 4 pool.map()的解析
- pool.map() 的基本用法
- 返回值
- 语法
- 示例
- 注意事项
- 适用场景
- 5 pool.join()详解
- 示例
- 注意事项
- pool.join()的运行逻辑
- 阻塞特性的影响
- 对计算速度的影响
- 示例
- 总结
- 6 apply_async(), apply(), 和 pool.map()
- `apply_async()`
- 特性:
- 语法:
- `apply()`
- 特性:
- 语法:
- `pool.map()`
- 特性:
- 语法:
- 原理
- 示例
- 使用 `apply_async()`
- 使用 `apply()`
- 使用 `pool.map()`
- 总结
- 7callback和get方法
- `callback` 方法
- 使用示例
- `get()` 方法
- 使用示例
- 总结
- 何时使用
- 8 start的使用
- 使用 `start` 方法
- 示例
- 何时使用 `start`
- 使用 `run` 方法
- 总结
1 多进程解释
在Python中,多进程是一种利用操作系统的多核或多处理器能力来并行执行任务的方法。Python的标准库提供了multiprocessing模块来支持多进程编程。
多进程
多进程是指使用多个进程同时运行程序的不同部分。在Python中,由于全局解释器锁(GIL)的存在,即使在多线程环境中,也不能充分利用多核CPU的优势。因此,使用多进程可以绕过GIL,从而实现真正的并行计算。
进程池
进程池是一个包含多个工作进程的对象。它会预先创建一定数量的进程,并将任务分发给这些进程处理。这样可以避免频繁创建和销毁进程带来的开销。当一个任务完成时,该进程就可以被用来执行下一个任务。
2 进程的演示
import multiprocessing
import time
def worker(num):
"""模拟任务"""
print(f"Worker {num} started")
time.sleep(2) # 模拟耗时任务
print(f"Worker {num} finished")
if __name__ == "__main__":
processes = []
# 创建进程
for i in range(4):
p = multiprocessing.Process(target=worker, args=(i,))
processes.append(p)
p.start()
# 等待所有进程结束
for p in processes:
p.join()
print("All workers finished.")
在这个例子中,我们创建了四个进程,每个进程都执行worker函数。我们首先创建了这些进程,并使用start()方法启动它们。然后使用join()方法等待所有子进程完成。然后结束主进程。
3 进程池方法
进程池则是一种更加高效的方式来管理多个进程。进程池会在开始时创建一定数量的进程,并将任务分配给这些进程执行。
示例代码
import multiprocessing
def square(x):
return x * x
if __name__ == "__main__":
with multiprocessing.Pool(processes=4) as pool:
numbers = [1, 2, 3, 4, 5]
# 使用 map 方法
result = pool.map(square, numbers)
print("Results:", result)
# 使用 apply_async 方法
async_result = []
for number in numbers:
res = pool.apply_async(square, (number,))
async_result.append(res)
for r in async_result:
print(r.get()) # 获取结果
pool.close()
pool.join()
在这个例子中,我们创建了一个包含4个进程的进程池。我们使用pool.map()方法来并行地对列表中的每一个元素应用square函数。此外,我们还展示了如何使用apply_async()方法异步地执行任务,并通过get()方法获取结果。
4 pool.map()的解析
pool.map()
是 Python 的 multiprocessing
模块中的一个方法,用于将一个可迭代对象中的元素分布到进程池中的各个进程上进行并行处理。它类似于内置的 map()
函数,但专门设计用于多进程环境。
pool.map() 的基本用法
pool.map()
接受两个参数:
function
: 一个可调用对象,将在每个进程上并行执行。iterable
: 一个可迭代对象,其元素将作为参数传递给function
。
返回值
pool.map()
返回一个列表,其中的元素是按照输入可迭代对象的顺序排列的结果。
语法
result = pool.map(function, iterable[, chunksize])
chunksize
是一个可选参数,用于控制每次提交给进程池的任务的数量。如果不指定,将自动计算一个合适的值。
示例
下面是一个使用 pool.map()
的简单示例:
import multiprocessing
def square(x):
return x * x
if __name__ == "__main__":
with multiprocessing.Pool(processes=4) as pool:
numbers = [1, 2, 3, 4, 5]
# 使用 map 方法
result = pool.map(square, numbers)
print("Results:", result)
在这个例子中,我们定义了一个简单的函数 square
,它返回传入数字的平方。然后我们创建了一个包含4个进程的进程池,并使用 pool.map()
将 numbers
列表中的每个元素传递给 square
函数。
注意事项
- 阻塞性:
pool.map()
是一个阻塞调用,意味着它会等待所有任务完成后再返回结果。 - 数据类型:
pool.map()
只能处理可以序列化的输入参数和返回值。这是因为数据需要在进程间传输,而 Python 的进程间通信机制(如 Pipe 和 Queue)只能传输可以序列化的数据。 - 错误处理:如果在某个进程中发生错误,
pool.map()
会抛出异常。可以通过 try-except 块来捕获这些异常。
适用场景
pool.map()
最适合用于那些可以很容易地将任务分解成独立单元的情况,例如对一个列表中的每个元素进行相同的计算操作。这种方法非常适合 CPU 密集型任务,如数值计算或图像处理。
通过使用 pool.map()
,你可以有效地利用多核处理器的并行处理能力来加速程序的执行。
5 pool.join()详解
pool.join()
的作用是在使用Python的multiprocessing
模块中的Pool
类时确保所有的子进程都已经完成执行。当你使用进程池(Pool
)来执行任务时,通常会先调用close()
方法来禁止向进程池添加新的任务,然后再调用join()
方法来等待所有已经提交的任务完成。
pool.join()详解
-
关闭进程池:
- 在调用
join()
之前,通常需要先调用pool.close()
。close()
方法确保不会再有新的任务提交到进程池中。
pool.close()
- 在调用
-
等待任务完成:
join()
方法则等待所有已提交的任务完成执行。这意味着主进程会暂停执行,直到进程池中的所有工作进程都完成了它们的任务。
pool.join()
示例
以下是一个使用Pool
类的示例,演示了如何正确使用close()
和join()
方法:
import multiprocessing
def square(x):
return x * x
if __name__ == "__main__":
with multiprocessing.Pool(processes=4) as pool:
numbers = [1, 2, 3, 4, 5]
# 使用 map 方法
result = pool.map(square, numbers)
print("Results:", result)
# 使用 apply_async 方法
async_result = []
for number in numbers:
res = pool.apply_async(square, (number,))
async_result.append(res)
for r in async_result:
print(r.get()) # 获取结果
# 关闭进程池,不再接受新任务
pool.close()
# 等待所有任务完成
pool.join()
在这个例子中,我们首先使用pool.map()
来并行处理一组数字。然后,我们使用apply_async()
来异步地执行任务,并通过get()
方法获取结果。最后,我们调用pool.close()
来阻止进一步的任务提交,然后调用pool.join()
来等待所有任务完成。
注意事项
- 调用
join()
前务必先调用close()
,否则可能会引发异常。 - 如果不使用
with
语句管理进程池,那么在调用join()
之后,通常还需要调用pool.terminate()
来清理进程池资源。
通过这种方式,你可以确保在继续执行主程序之前,所有的任务都已经完成执行。这对于确保数据一致性以及避免潜在的资源泄漏非常重要。
pool.join()的运行逻辑
pool.map()
方法确实是一个阻塞调用,这意味着它会等待所有任务完成并收集结果后才会返回。尽管如此,这并不一定会显著影响整体计算速度,特别是对于CPU密集型任务而言。下面是一些关键点来说明这一点:
阻塞特性的影响
-
等待所有任务完成:
pool.map()
会等待所有提交给进程池的任务完成,并且会按顺序返回结果。这意味着在调用pool.map()
后,主进程会暂停执行,直到所有任务完成。 -
CPU 密集型任务:对于 CPU 密集型任务来说,阻塞特性不会显著影响性能,因为主要瓶颈在于 CPU 计算而非 I/O 或其他延迟操作。在这种情况下,进程池可以充分利用多核处理器的能力来并行执行任务。
对计算速度的影响
-
并行计算的优势:对于能够有效利用多核 CPU 的任务,
pool.map()
提供的并行计算可以大大加快计算速度。每个进程都会独立执行任务的一部分,最终合并结果。 -
任务分解:如果任务可以很好地分解为独立的小任务,那么使用
pool.map()
就非常合适。这种方法可以提高整体效率,尤其是当任务数量大于处理器核心数时。 -
任务粒度:任务的粒度(即单个任务的大小)也会影响性能。较大的任务粒度可能更适合并行化,因为这样可以减少任务调度的开销。
示例
假设我们有一个计算密集型任务,比如计算一个大列表中每个元素的平方。我们可以使用 pool.map()
来加速这个过程:
import multiprocessing
def square(x):
return x * x
if __name__ == "__main__":
with multiprocessing.Pool(processes=4) as pool:
numbers = list(range(1, 1000001)) # 生成一个从 1 到 1000000 的列表
# 使用 map 方法
start_time = time.time()
result = pool.map(square, numbers)
end_time = time.time()
print("Results:", result[:5]) # 输出前五个结果
print("Time taken:", end_time - start_time, "seconds")
在这个例子中,我们使用 pool.map()
来并行计算列表中每个元素的平方。尽管 pool.map()
是阻塞的,但由于计算密集型任务的性质,这种阻塞并不会导致显著的性能损失。
总结
- 对于 CPU 密集型任务,
pool.map()
的阻塞性不会显著影响计算速度,反而可以大大提高处理速度。 - 对于 I/O 密集型任务或需要频繁交互的任务,可能需要考虑非阻塞方法或使用其他并行技术。
因此,在选择是否使用 pool.map()
时,请根据具体的任务类型和要求来决定。如果你的任务主要是计算密集型的,那么 pool.map()
是一个很好的选择。
6 apply_async(), apply(), 和 pool.map()
apply_async()
, apply()
, 和 pool.map()
是 Python 的 multiprocessing
模块中用于并行处理任务的三种不同方法。它们在使用场景、参数处理、结果获取等方面有所不同。下面我将详细介绍它们的异同点及原理。
apply_async()
apply_async()
方法用于异步地将参数传递给一个函数并在进程池中的一个进程中执行该函数。它可以处理任意数量的参数,并允许指定回调函数来处理结果。
特性:
- 异步调用:
apply_async()
是一个非阻塞调用,它可以立即返回一个结果对象,而不需要等待任务完成。 - 参数灵活性:可以接受任意数量的位置参数和关键字参数。
- 单一进程:每次调用
apply_async()
会将任务分配给进程池中的一个进程,而不是像pool.map()
那样将多个任务分发给多个进程。 - 结果获取:通过
get()
方法从结果对象中获取结果。也可以通过callback
参数指定一个回调函数来处理结果。
语法:
result_object = pool.apply_async(function, args[, kwds][, callback][, error_callback])
apply()
apply()
方法用于同步地将参数传递给一个函数并在进程池中的一个进程中执行该函数。它可以处理任意数量的参数。
特性:
- 阻塞调用:
apply()
是一个阻塞调用,它会等待任务完成并返回结果。 - 参数灵活性:可以接受任意数量的位置参数和关键字参数。
- 单一进程:每次调用
apply()
会将任务分配给进程池中的一个进程。 - 结果获取:直接返回结果。
语法:
result = pool.apply(function, args[, kwds])
pool.map()
pool.map()
方法用于将一个可迭代对象中的元素分布到进程池中的各个进程上进行并行处理。它类似于内置的 map()
函数,但专门设计用于多进程环境。
特性:
- 阻塞调用:
pool.map()
是一个阻塞调用,它会等待所有任务完成并返回结果。 - 参数限制:只能处理单一可迭代参数。如果需要处理多个参数,需要先将它们组合成一个元组或列表。
- 结果顺序:返回的结果列表与输入的可迭代对象保持相同的顺序。
- 并行处理:每个元素会被分配给进程池中的一个进程进行处理。
语法:
result = pool.map(function, iterable[, chunksize])
原理
-
apply_async()
:- 使用
apply_async()
时,你可以在等待任务完成的同时继续执行其他代码。它返回一个结果对象,你可以通过调用get()
方法来获取结果,或者通过callback
参数指定一个回调函数来处理结果。
- 使用
-
apply()
:- 使用
apply()
时,它会等待任务完成并直接返回结果。这是一种简单的同步调用,适合处理单一任务。
- 使用
-
pool.map()
:- 使用
pool.map()
时,它会等待所有任务完成并返回一个结果列表。它适合处理可以容易地分解为独立任务的情况,特别是当任务数量较大时。
- 使用
示例
使用 apply_async()
import multiprocessing
def square(x):
return x * x
def callback(result):
print("Result:", result)
if __name__ == "__main__":
with multiprocessing.Pool(processes=4) as pool:
for i in range(5):
pool.apply_async(square, (i,), callback=callback)
pool.close()
pool.join()
使用 apply()
import multiprocessing
def square(x):
return x * x
if __name__ == "__main__":
with multiprocessing.Pool(processes=4) as pool:
for i in range(5):
result = pool.apply(square, (i,))
print("Result:", result)
使用 pool.map()
import multiprocessing
def square(x):
return x * x
if __name__ == "__main__":
with multiprocessing.Pool(processes=4) as pool:
numbers = [1, 2, 3, 4, 5]
# 使用 map 方法
result = pool.map(square, numbers)
print("Results:", result)
总结
-
apply_async()
:- 适用于需要异步执行任务的情况,可以立即返回结果对象。
- 适合处理单个任务,可以处理任意数量的参数。
-
apply()
:- 适用于需要同步执行任务的情况,会等待任务完成并直接返回结果。
- 适合处理单个任务,可以处理任意数量的参数。
-
pool.map()
:- 适用于将单一可迭代参数中的每个元素并行处理。
- 返回结果列表,结果顺序与输入参数一致。
- 通常用于处理大量相似任务。
根据你的具体需求选择合适的方法。如果你的任务可以很容易地分解为独立的、可以并行处理的任务,并且这些任务只需要单一参数,那么 pool.map()
是一个很好的选择。如果你的任务参数较为复杂,或者你需要更灵活地处理任务,那么 apply_async()
或 apply()
可能更适合。
7callback和get方法
在 Python 的 multiprocessing
模块中,callback
和 get()
方法通常与 apply_async()
方法一起使用,用于处理异步任务的结果。
callback
方法
callback
是一个可选参数,可以在调用 apply_async()
时指定。当异步任务完成后,callback
函数会被调用,并将任务的结果作为参数传递给它。
使用示例
import multiprocessing
def square(x):
return x * x
def callback(result):
print("Result:", result)
if __name__ == "__main__":
with multiprocessing.Pool(processes=4) as pool:
for i in range(5):
pool.apply_async(square, (i,), callback=callback)
pool.close()
pool.join()
在这个例子中,我们定义了一个简单的函数 square
,它返回传入数字的平方。我们使用 apply_async()
方法来异步地执行任务,并通过 callback
参数来指定一个回调函数来处理结果。当我们调用 pool.close()
和 pool.join()
时,所有任务都已经被提交,并且主程序会等待所有任务完成。
get()
方法
get()
方法用于从异步任务的结果对象中获取结果。当你使用 apply_async()
方法时,它会返回一个结果对象。你可以通过调用 get()
方法来等待任务完成并获取结果。
使用示例
import multiprocessing
def square(x):
return x * x
if __name__ == "__main__":
with multiprocessing.Pool(processes=4) as pool:
results = []
for i in range(5):
result = pool.apply_async(square, (i,))
results.append(result)
for r in results:
print("Result:", r.get()) # 获取结果
pool.close()
pool.join()
在这个例子中,我们同样定义了一个简单的函数 square
。我们使用 apply_async()
方法来异步地执行任务,并将返回的结果对象存储在一个列表中。然后我们遍历这个列表,通过调用 get()
方法来获取每个任务的结果。
总结
-
callback
:- 用于指定一个回调函数来处理异步任务的结果。
- 回调函数会在任务完成后被自动调用。
-
get()
:- 用于从结果对象中获取异步任务的结果。
- 会等待任务完成并返回结果。
何时使用
- 如果你需要在任务完成后立即处理结果,并且想要避免阻塞主程序的执行,那么使用
callback
是一个好选择。 - 如果你只需要在所有任务完成后处理结果,并且不介意等待所有任务完成,那么使用
get()
方法更为简单。
根据你的具体需求选择合适的方法。如果你的任务可以很容易地分解为独立的、可以并行处理的任务,并且需要立即处理结果,那么使用 callback
可能更合适。如果你的任务参数较为复杂,或者只需要在所有任务完成后处理结果,那么使用 get()
方法可能更方便。
8 start的使用
在 Python 的 multiprocessing
模块中,start
方法通常用于启动一个单独的进程。当你创建了一个 Process
对象时,你需要调用 start
方法来启动这个进程。
使用 start
方法
当你创建了一个 Process
对象时,你需要调用它的 start
方法来启动进程。一旦调用了 start
方法,进程就会开始执行 target
函数。
示例
import multiprocessing
import time
def worker():
print("Worker started")
time.sleep(2) # 模拟耗时操作
print("Worker finished")
if __name__ == "__main__":
p = multiprocessing.Process(target=worker)
p.start() # 启动进程
# 主程序可以继续执行其他操作
print("Main program continues...")
p.join() # 等待进程完成
print("Process finished.")
在这个例子中,我们创建了一个名为 worker
的简单函数,它打印一条消息,然后模拟一个耗时的操作,最后再次打印一条消息。我们创建了一个 Process
对象,并指定了 worker
函数作为目标函数。接着,我们调用 start
方法来启动进程,并在主程序中继续执行其他操作。最后,我们调用 join
方法来等待进程完成。
何时使用 start
- 启动独立进程:当你需要创建一个新的进程来执行特定任务时,你应该使用
start
方法。 - 并行执行任务:当你希望在主线程继续执行的同时,另一个进程也在后台执行时,你应该使用
start
方法。 - 避免阻塞:如果你不希望主线程等待子进程完成,那么应该使用
start
方法,而不是run
方法。
使用 run
方法
需要注意的是,run
方法并不是用来启动进程的。run
方法是 Process
类的一个内部方法,它实际上包含了进程执行的逻辑。当你创建一个 Process
对象时,实际上是 run
方法在内部被调用来执行 target
函数。通常情况下,你不应该直接调用 run
方法。
总结
-
start
方法:- 用于启动进程。
- 应该在创建
Process
对象后调用。 - 允许进程在后台执行,不影响主线程的继续执行。
-
run
方法:- 是
Process
类的内部方法,不应直接调用。 - 包含了执行
target
函数的逻辑。
- 是
当你需要创建并启动一个单独的进程来执行特定任务时,你应该使用 start
方法。这使得你可以在主线程继续执行其他操作的同时,让新进程在后台执行任务。