web-dev-qa-db-ja.com

pythonコードの一部を並行して実行する簡単な方法は?

私はこれを非常に単純にしていますpython code:

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()自体(self-)からもそれを強制終了する方法を知りたいと思います。終了)。

スレッドに関するチュートリアルをいくつか読んだことがありますが、ほとんどすべてのチュートリアルでアプローチが異なり、さらにいくつかのチュートリアルを理解するのに苦労したので、コードを並行して実行する最も簡単な方法を知りたいと思います。

ありがとうございました。

9
Askerman

さて、最初に、ここにあなたの質問への答えが逐語的にそして可能な限り簡単な方法であります。その後、これを実行し、メインコードと並列コード間でデータへのアクセスを共有する2つの方法を示す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)マルチプロセッシングインターフェイスを使用した並列実行を示す2つのコード例(以下にリスト)を示します。これらのどれを使用するかは、何をしようとしているかによって異なります。 2番目のスレッドの目的がI/Oを待機することである場合、スレッド化は適切な選択であり、2番目のスレッドがCPUを集中的に計算するための場合、マルチプロセッシングは適切な選択です。

あなたの例では、メインコードが変数を変更し、パラレルコードは変数のみを調べました。共有カウンターをリセットするなど、両方から変数を変更する場合は、状況が異なります。それで、それを行う方法も紹介します。

次のサンプルコードでは:

1)変数 "counter"および "run"および "lock"は、メインプログラムと並行して実行されるコード間で共有されます。

2)関数myfunc()は、並行して実行されます。メインプログラムによってrunがfalseに設定されるまで、counterの更新とスリープをループします。

3)メインプログラムは、counterの値を5に達するまで出力をループし、5に達すると、カウンターをリセットします。次に、再び5に達した後、runをfalseに設定し、最後に、スレッドまたはプロセスが終了するのを待ってから終了します。

最初の例では、counterlocal.acquire()およびlock.release()の呼び出し内でインクリメントされていることに気付くかもしれません。 lock 2番目の例では。

カウンタのインクリメントは、(1)現在の値を読み取る、(2)値を加算する、(3)結果をカウンタに保存するという3つのステップで構成されます。問題は、これが発生しているのと同時に1つのスレッドがカウンターを設定しようとしたときに発生します。

これを解決するには、メインプログラムと並列コードの両方で、変数を変更する前にlockを取得し、変更が完了するとreleaseを取得します。ロックがすでに取得されている場合、プログラムまたは並列コードはロックが解除されるまで待機します。このは、共有データ、つまりカウンターを変更するためのアクセスを同期します。 (余談ですが、別の種類の同期については、セマフォを参照してください)。

その紹介で、スレッドを使用する最初の例を次に示します。

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

そして、これがマルチプロセッシングを使用する2番目の例です。共有変数にアクセスするには、いくつかの追加の手順が必要であることに注意してください。

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)
2
DrM

手動でスレッドを開始するよりも、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:map_asyncとimapの違いは何ですか?

また、(リストに加えて)複数の変数を渡すために使用できるfunctools.partialsもチェックする価値があります。

別のトリック:(プロセッサの複数のコアのように)実際にはマルチプロセッシングが必要ない場合もありますが、同時に多くの接続を持つデータベースに同時にクエリを実行するには、複数のスレッドだけが必要です。その場合、multiprocessing.dummy import Poolから実行するだけで、pythonが別のプロセスを生成するのを防ぐことができます(これにより、関数に渡さないすべての名前空間にアクセスできなくなります)。単一のCPUコアだけで、プールのすべての利点を維持します。pythonマルチプロセッシング(複数のコアを使用)およびマルチスレッド(1つのプロセスのみを使用し、グローバルを維持する)について知っておく必要があるのはこれだけです。インタプリタロックはそのまま)。

もう1つの小さなアドバイス:常にプールなしで最初にマップを使用するようにしてください。すべてが機能することを確認したら、次の手順でpool.imapに切り替えます。

1
Nickpick