繁体   English   中英

一种并行运行一段python代码的简单方法?

[英]A simple way to run a piece of python code in parallel?

我有这个非常简单的python代码:

Test = 1;

def para():
   while(True):
      if Test > 10:
         print("Test is bigger than ten");
      time.sleep(1);

para(); # I want this to start in parallel, so that the code below keeps executing without waiting for this function to finish

while(True):
   Test = random.randint(1,42);
   time.sleep(1);

   if Test == 42:
       break;

...#stop the parallel execution of the para() here (kill it)

..some other code here

基本上,我想与其他代码并行运行函数 para(),这样它下面的代码就不必等待 para() 结束。 但是,我希望能够在 para() 并行运行时访问 Test 变量的当前值(如上面的代码示例所示)。 后来,当我决定完成并行运行的 para() 时,我想知道如何从主线程以及从并行运行的 para() 本身(自终止)。

我已经阅读了一些关于线程的教程,但几乎每个教程都以不同的方式处理它,而且我在理解其中一些方面遇到了麻烦,所以我想知道并行运行一段代码的最简单方法是什么。

谢谢你。

好的,首先,这里以最简单的方式逐字逐句地回答您的问题。 之后,我们通过两个示例来更全面地回答,这些示例展示了两种方法来执行此操作并在主代码和并行代码之间共享对数据的访问。

import random

from threading import Thread
import time

Test = 1;
stop = False

def para():
   while not stop:
      if Test > 10:
         print("Test is bigger than ten");
      time.sleep(1);

# I want this to start in parallel, so that the code below keeps executing without waiting for this function to finish

thread = Thread(target=para)
thread.start()

while(True):
   Test = random.randint(1,42);
   time.sleep(1);

   if Test == 42:
       break;

#stop the parallel execution of the para() here (kill it)
stop = True
thread.join()

#..some other code here
print( 'we have stopped' )

现在,更完整的答案是:

在下文中,我们展示了两个代码示例(如下所列),它们演示了 (a) 使用线程接口并行执行,以及 (b) 使用多处理接口。 您选择使用其中的哪一个,取决于您要尝试做什么。 当第二个线程的目的是等待 I/O 时,线程可能是一个不错的选择,而当第二个线程用于执行 CPU 密集型计算时,多处理可能是一个不错的选择。

在您的示例中,主代码更改了一个变量,而并行代码仅检查了该变量。 如果您想从两者中更改变量,则情况有所不同,例如重置共享计数器。 因此,我们也将向您展示如何做到这一点。

在以下示例代码中:

  1. 变量“ counter ”、“ run ”和“ lock ”在主程序和并行执行的代码之间共享。

  2. 函数myfunc()是并行执行的。 它循环更新计数器和睡眠,直到主程序将run设置为 false。

  3. 主程序循环打印计数器的值,直到它达到 5,此时它会重置计数器。 然后,在它再次达到 5 后,它将run设置为 false,最后,它在退出之前等待线程或进程退出。

您可能会注意到,在第一个示例中,在调用lock.acquire()lock.release()或在第二个示例中使用lock时, counter会递增。

递增计数器包括三个步骤,(1) 读取当前值,(2) 加一,然后 (3) 将结果存回计数器。 当一个线程试图在发生这种情况的同时设置计数器时,问题就出现了。

我们通过让主程序和并行代码在更改变量之前获取,然后在完成后释放它来解决这个问题。 如果锁已经被占用,程序或并行代码会等待直到它被释放。 这将同步他们的访问以更改共享数据,即计数器。 (另外,另一种同步参见信号量)。

有了这个介绍,这是第一个使用线程的示例:

# Parallel code with shared variables, using threads
from threading import Lock, Thread
from time import sleep

# Variables to be shared across threads
counter = 0
run = True
lock = Lock()

# Function to be executed in parallel
def myfunc():

    # Declare shared variables
    global run
    global counter
    global lock

    # Processing to be done until told to exit
    while run:
        sleep( 1 )

        # Increment the counter
        lock.acquire()
        counter = counter + 1
        lock.release()

    # Set the counter to show that we exited
    lock.acquire()
    counter = -1
    lock.release()
    print( 'thread exit' )

# ----------------------------

# Launch the parallel function as a thread
thread = Thread(target=myfunc)
thread.start()

# Read and print the counter
while counter < 5:
    print( counter )
    sleep( 1 )

# Change the counter    
lock.acquire()
counter = 0
lock.release()

# Read and print the counter
while counter < 5:
    print( counter )
    sleep( 1 )
    
# Tell the thread to exit and wait for it to exit
run = False
thread.join()

# Confirm that the thread set the counter on exit
print( counter )

这是第二个示例,它使用多处理。 请注意,访问共享变量需要一些额外的步骤。

from time import sleep
from multiprocessing import Process, Value, Lock

def myfunc(counter, lock, run):
    
    while run.value:
        sleep(1)
        with lock:
            counter.value += 1
            print( "thread %d"%counter.value )

    with lock:
        counter.value = -1
        print( "thread exit %d"%counter.value )

# =======================

counter = Value('i', 0)
run = Value('b', True)
lock = Lock()

p = Process(target=myfunc, args=(counter, lock, run))
p.start()

while counter.value < 5:
    print( "main %d"%counter.value )
    sleep(1)

with lock:
    counter.value = 0
    
while counter.value < 5:
    print( "main %d"%counter.value )
    sleep(1)

run.value = False

p.join()

print( "main exit %d"%counter.value)

与其手动启动线程,不如使用 multiprocessing.pool。 多处理部分需要在您使用 map 调用的函数中。 然后您可以使用 pool.imap 而不是 map。

import multiprocessing
import time
def func(x):
    time.sleep(x)
    return x + 2

if __name__ == "__main__":    
    p = multiprocessing.Pool()
    start = time.time()
    for x in p.imap(func, [1,5,3]):
        print("{} (Time elapsed: {}s)".format(x, int(time.time() - start)))

另请查看: multiprocessing.Pool:map_async 和 imap 之间有什么区别?

同样值得一试的是 functools.partials ,它可用于传入多个变量(除了列表)。

另一个技巧:有时您并不真正需要多处理(如在处理器的多个内核中),而只需要多个线程来同时查询具有多个连接的数据库。 在这种情况下,只需执行 from multiprocessing.dummy import Pool 并可以避免 python 产生一个单独的进程(这会使您无法访问所有未传递给函数的名称空间),但保留池的所有好处,只是在单个 CPU 内核中。 这就是您需要了解的有关 python 多处理(使用多核)和多线程(仅使用一个进程并保持全局解释器锁完好无损)的全部内容。

另一个小建议:始终尝试先使用 map 而没有任何池。 一旦确定一切正常,然后在下一步中切换到 pool.imap。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM