python使用多进程multiprocessing

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() 接受两个参数:

  1. function: 一个可调用对象,将在每个进程上并行执行。
  2. 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 函数。

注意事项

  1. 阻塞性pool.map() 是一个阻塞调用,意味着它会等待所有任务完成后再返回结果。
  2. 数据类型pool.map() 只能处理可以序列化的输入参数和返回值。这是因为数据需要在进程间传输,而 Python 的进程间通信机制(如 Pipe 和 Queue)只能传输可以序列化的数据。
  3. 错误处理:如果在某个进程中发生错误,pool.map() 会抛出异常。可以通过 try-except 块来捕获这些异常。

适用场景

pool.map() 最适合用于那些可以很容易地将任务分解成独立单元的情况,例如对一个列表中的每个元素进行相同的计算操作。这种方法非常适合 CPU 密集型任务,如数值计算或图像处理。

通过使用 pool.map(),你可以有效地利用多核处理器的并行处理能力来加速程序的执行。

5 pool.join()详解

pool.join()的作用是在使用Python的multiprocessing模块中的Pool类时确保所有的子进程都已经完成执行。当你使用进程池(Pool)来执行任务时,通常会先调用close()方法来禁止向进程池添加新的任务,然后再调用join()方法来等待所有已经提交的任务完成。

pool.join()详解

  1. 关闭进程池

    • 在调用join()之前,通常需要先调用pool.close()close()方法确保不会再有新的任务提交到进程池中。
    pool.close()
    
  2. 等待任务完成

    • 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密集型任务而言。下面是一些关键点来说明这一点:

阻塞特性的影响

  1. 等待所有任务完成pool.map() 会等待所有提交给进程池的任务完成,并且会按顺序返回结果。这意味着在调用 pool.map() 后,主进程会暂停执行,直到所有任务完成。

  2. CPU 密集型任务:对于 CPU 密集型任务来说,阻塞特性不会显著影响性能,因为主要瓶颈在于 CPU 计算而非 I/O 或其他延迟操作。在这种情况下,进程池可以充分利用多核处理器的能力来并行执行任务。

对计算速度的影响

  1. 并行计算的优势:对于能够有效利用多核 CPU 的任务,pool.map() 提供的并行计算可以大大加快计算速度。每个进程都会独立执行任务的一部分,最终合并结果。

  2. 任务分解:如果任务可以很好地分解为独立的小任务,那么使用 pool.map() 就非常合适。这种方法可以提高整体效率,尤其是当任务数量大于处理器核心数时。

  3. 任务粒度:任务的粒度(即单个任务的大小)也会影响性能。较大的任务粒度可能更适合并行化,因为这样可以减少任务调度的开销。

示例

假设我们有一个计算密集型任务,比如计算一个大列表中每个元素的平方。我们可以使用 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() 方法用于异步地将参数传递给一个函数并在进程池中的一个进程中执行该函数。它可以处理任意数量的参数,并允许指定回调函数来处理结果。

特性:
  1. 异步调用apply_async() 是一个非阻塞调用,它可以立即返回一个结果对象,而不需要等待任务完成。
  2. 参数灵活性:可以接受任意数量的位置参数和关键字参数。
  3. 单一进程:每次调用 apply_async() 会将任务分配给进程池中的一个进程,而不是像 pool.map() 那样将多个任务分发给多个进程。
  4. 结果获取:通过 get() 方法从结果对象中获取结果。也可以通过 callback 参数指定一个回调函数来处理结果。
语法:
result_object = pool.apply_async(function, args[, kwds][, callback][, error_callback])

apply()

apply() 方法用于同步地将参数传递给一个函数并在进程池中的一个进程中执行该函数。它可以处理任意数量的参数。

特性:
  1. 阻塞调用apply() 是一个阻塞调用,它会等待任务完成并返回结果。
  2. 参数灵活性:可以接受任意数量的位置参数和关键字参数。
  3. 单一进程:每次调用 apply() 会将任务分配给进程池中的一个进程。
  4. 结果获取:直接返回结果。
语法:
result = pool.apply(function, args[, kwds])

pool.map()

pool.map() 方法用于将一个可迭代对象中的元素分布到进程池中的各个进程上进行并行处理。它类似于内置的 map() 函数,但专门设计用于多进程环境。

特性:
  1. 阻塞调用pool.map() 是一个阻塞调用,它会等待所有任务完成并返回结果。
  2. 参数限制:只能处理单一可迭代参数。如果需要处理多个参数,需要先将它们组合成一个元组或列表。
  3. 结果顺序:返回的结果列表与输入的可迭代对象保持相同的顺序。
  4. 并行处理:每个元素会被分配给进程池中的一个进程进行处理。
语法:
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() 可能更适合。

阻塞式 Map 和 Apply 的异同

在并行编程中,“map”和“apply”是两种常见的函数式编程模式,它们可以用来描述如何将操作应用于集合中的元素。在多线程或多进程中,这些方法通常被设计为支持并行处理。下面我们将讨论“map”的阻塞式版本和“apply”的阻塞式版本之间的异同。

阻塞式 Map 和 Apply 的定义
阻塞式 Map
  • 定义map 函数通常接受一个可迭代对象和一个函数作为参数,并对可迭代对象中的每一个元素应用该函数。在阻塞式 map 中,函数会在所有元素上完成后再返回结果。
  • 示例:假设有一个列表 [1, 2, 3] 和一个函数 f(x) = x * 2,那么 map(f, [1, 2, 3]) 将会返回 [2, 4, 6]
阻塞式 Apply
  • 定义apply 函数通常用于向一个函数传递一组参数,并调用该函数。在阻塞式 apply 中,函数会在完成所有操作后返回结果。
  • 示例:假设有一个函数 g(x, y) = x + y,并且我们想要计算 g(1, 2)g(3, 4),那么我们可以使用 apply(g, [(1, 2), (3, 4)]) 来获取结果。
相同点
  1. 阻塞性:两者都是阻塞式的,意味着它们会在所有任务完成之后才返回结果。
  2. 并行处理:在多线程或多进程环境中,它们都可以被设计成支持并行处理。
  3. 等待完成:无论是 map 还是 apply,都需要等待所有任务完成后才会返回最终的结果。
不同点
  1. 输入格式

    • Map:通常接受一个可迭代对象和一个函数。
    • Apply:通常接受一个函数和一个参数列表。
  2. 处理方式

    • Map:将函数应用于每个元素。
    • Apply:将参数列表传递给函数。
  3. 应用场景

    • Map:适用于对集合中的每个元素进行相同的操作。
    • Apply:适用于对不同参数集调用同一个函数。
示例代码

假设我们使用 Python 的 multiprocessing 模块来实现这两个功能:

import multiprocessing

def square(x):
    return x * x

def sum_xy(x, y):
    return x + y

if __name__ == '__main__':
    # 使用阻塞式 map
    with multiprocessing.Pool() as pool:
        result_map = pool.map(square, [1, 2, 3])
        print("Result of map:", result_map)

    # 使用阻塞式 apply
    with multiprocessing.Pool() as pool:
        result_apply = pool.apply(sum_xy, (1, 2))
        print("Result of apply:", result_apply)

在这个例子中,pool.map() 是一个阻塞式 map 实现,它会等待所有元素处理完毕再返回结果。而 pool.apply() 则是一个阻塞式 apply 实现,它也等待计算完成后返回结果。

总结来说,虽然两者都支持阻塞式的操作,并且可以用于并行处理,但它们在输入格式和处理方式上有所不同。在实际编程中,根据具体的任务类型和需求来选择合适的方法。

7callback和get方法

在 Python 的 multiprocessing 模块中,callbackget() 方法通常与 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 方法。这使得你可以在主线程继续执行其他操作的同时,让新进程在后台执行任务。

9 关键字传参

在Python的multiprocessing模块中,通常我们是通过位置参数来传递给子进程的函数。然而,如果你想使用关键字参数来传递参数,你可以通过定义一个接受字典作为参数的函数来实现这一点。

下面是一个使用关键字参数的例子:

from multiprocessing import Process

def worker(data):
    name = data['name']
    age = data['age']
    print(f'Worker received name={name}, age={age}')

if __name__ == '__main__':
    data = {'name': 'Alice', 'age': 25}
    
    p = Process(target=worker, args=(data,))
    p.start()
    p.join()

在这个例子中,我们将一个包含关键字参数的字典传递给了worker函数。这样做的好处是可以在主进程中灵活地构造参数,并且保持代码清晰。

如果你希望直接使用关键字参数,也可以考虑使用concurrent.futures.ProcessPoolExecutor,它支持直接使用关键字参数:

from concurrent.futures import ProcessPoolExecutor

def worker(name, age):
    print(f'Worker received name={name}, age={age}')

if __name__ == '__main__':
    with ProcessPoolExecutor() as executor:
        executor.submit(worker, name='Alice', age=25)

这个方法使用了ProcessPoolExecutor来启动进程,并直接使用了关键字参数。这种方式更加直观,但是需要注意的是,ProcessPoolExecutor在某些情况下可能不如直接使用multiprocessing模块灵活。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小蜗笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值