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

时间:2018-07-01 14:01:01

标签: python multithreading python-3.x python-multithreading

我有这个非常简单的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()本身(自终止)。

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

谢谢。

2 个答案:

答案 0 :(得分:2)

好的,首先,这是您的问题的逐字回答,并且以最简单的方式进行。之后,我们用两个示例更充分地回答,这些示例显示了两种实现方法并在主代码和并行代码之间共享对数据的访问。

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)主程序和在其中执行的代码之间共享变量“ 计数器”,“ 运行”和“ 锁定”平行。

2)函数 myfunc()是并行执行的。

循环循环更新计数器和休眠,直到 run 设置为false。

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

在第一个示例中,您可能会注意到计数器在调用 local.acquire() lock.release()的过程中增加了,或在第二个示例中使用锁定

递增计数器包括三个步骤:(1)读取当前值,(2)将当前值加一,然后(3)将结果存储回计数器中。当一个线程试图同时设置计数器时就会出现问题。

我们通过在更改变量之前使主程序和并行代码都获得 lock ,然后在完成操作后释放来解决此问题。如果已获得锁定,则程序或并行代码将等待,直到释放为止。这会同步他们对更改共享数据(即计数器)的访问权限。 (此外,请参见信号量以获得另一种同步方式。)

在介绍之后,这是第一个使用线程的示例:

# 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)

答案 1 :(得分:1)

与其手动启动线程,不如直接使用multiprocessing.pool更好。多处理部分需要在您使用map调用的函数中。然后可以使用pool.imap代替地图。

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: What's the difference between map_async and imap?

也值得一试的是functools.partials,可以用来传递多个变量(除了列表之外)。

另一个技巧:有时您并不需要真正的多处理功能(例如在处理器的多个内核中),而只需多个线程即可同时并发查询具有多个连接的数据库。在那种情况下,只需从multiprocessing.dummy import Pool中执行操作,就可以避免python生成单独的进程(这使您无法访问未传递给函数的所有名称空间),但保留了pool的所有好处,只是在单个cpu内核中。这就是您需要了解的有关python多处理(使用多个内核)和多线程(仅使用一个进程并保持全局解释器锁定不变)的全部信息。

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