サーチ…


前書き

ほとんどのプログラムは1行ずつ実行され、一度に1つのプロセスしか実行されません。スレッドは、複数のプロセスが互いに独立して流れることを可能にします。複数のプロセッサを使用したスレッド化により、プログラムは複数のプロセスを同時に実行できます。このトピックでは、Pythonでのスレッドの実装と使用方法について説明します。

グローバルインタープリタロック

Pythonマルチスレッドのパフォーマンスは、 グローバルインタープリタロックのためにしばしば苦しんでいます。要するに、Pythonプログラムで複数のスレッドを持つことはできますが、CPUの数にかかわらず、一度に1つのバイトコード命令しか同時に実行できません。

したがって、ネットワークアクセスなどの外部イベントによって操作がブロックされている場合のマルチスレッドは、非常に効果的です。

import threading
import time


def process():
    time.sleep(2)


start = time.time()
process()
print("One run took %.2fs" % (time.time() - start))


start = time.time()
threads = [threading.Thread(target=process) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print("Four runs took %.2fs" % (time.time() - start))

# Out: One run took 2.00s
# Out: Four runs took 2.00s

processが実行に2秒かかりましたが、4つのプロセスを合計して合計2秒かかる並列処理を効果的に実行できることに注意してください。

しかし、多くの計算など、集中的な計算がPythonコードで実行されている場合のマルチスレッド化は、大幅な改善をもたらさず、並行実行よりも遅くなる可能性があります。

import threading
import time


def somefunc(i):
    return i * i

def otherfunc(m, i):
    return m + i

def process():
    for j in range(100):
        result = 0
        for i in range(100000):
            result = otherfunc(result, somefunc(i))


start = time.time()
process()
print("One run took %.2fs" % (time.time() - start))


start = time.time()
threads = [threading.Thread(target=process) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print("Four runs took %.2fs" % (time.time() - start))

# Out: One run took 2.05s
# Out: Four runs took 14.42s

後者の場合、複数のプロセスが同時に複数の命令を同時に実行できるため、マルチプロセッシングは効果的です。

import multiprocessing
import time


def somefunc(i):
    return i * i

def otherfunc(m, i):
    return m + i

def process():
    for j in range(100):
        result = 0
        for i in range(100000):
            result = otherfunc(result, somefunc(i))


start = time.time()
process()
print("One run took %.2fs" % (time.time() - start))


start = time.time()
processes = [multiprocessing.Process(target=process) for _ in range(4)]
for p in processes:
    p.start()
for p in processes:
    p.join()
print("Four runs took %.2fs" % (time.time() - start))

# Out: One run took 2.07s
# Out: Four runs took 2.30s

複数のスレッドで実行

threading.Threadを使用して、別のスレッドで関数を実行します。

import threading
import os

def process():
    print("Pid is %s, thread id is %s" % (os.getpid(), threading.current_thread().name))

threads = [threading.Thread(target=process) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()
    
# Out: Pid is 11240, thread id is Thread-1
# Out: Pid is 11240, thread id is Thread-2
# Out: Pid is 11240, thread id is Thread-3
# Out: Pid is 11240, thread id is Thread-4

複数のプロセスで実行する

別のプロセスで関数を実行するには、 multiprocessing.Processを使用します。インターフェイスはthreading.Threadと似ています:

import multiprocessing
import os

def process():
    print("Pid is %s" % (os.getpid(),))

processes = [multiprocessing.Process(target=process) for _ in range(4)]
for p in processes:
    p.start()
for p in processes:
    p.join()
    
# Out: Pid is 11206
# Out: Pid is 11207
# Out: Pid is 11208
# Out: Pid is 11209

スレッド間の状態の共有

すべてのスレッドが同じプロセスで実行されているため、すべてのスレッドは同じデータにアクセスできます。

ただし、同期の問題を回避するために、共有データへの同時アクセスをロックで保護する必要があります。

import threading

obj = {}
obj_lock = threading.Lock()

def objify(key, val):
    print("Obj has %d values" % len(obj))
    with obj_lock:
        obj[key] = val
    print("Obj now has %d values" % len(obj))

ts = [threading.Thread(target=objify, args=(str(n), n)) for n in range(4)]
for t in ts:
    t.start()
for t in ts:
    t.join()
print("Obj final result:")
import pprint; pprint.pprint(obj)

# Out: Obj has 0 values
# Out:  Obj has 0 values
# Out: Obj now has 1 values
# Out: Obj now has 2 valuesObj has 2 values
# Out: Obj now has 3 values
# Out: 
# Out:  Obj has 3 values
# Out: Obj now has 4 values
# Out: Obj final result:
# Out: {'0': 0, '1': 1, '2': 2, '3': 3}

プロセス間の状態の共有

異なるプロセスで実行されるコードは、デフォルトでは同じデータを共有しません。ただし、 multiprocessingモジュールには、複数のプロセス間で値を共有するためのプリミティブが含まれています。

import multiprocessing

plain_num = 0
shared_num = multiprocessing.Value('d', 0)
lock = multiprocessing.Lock()

def increment():
    global plain_num
    with lock:
        # ordinary variable modifications are not visible across processes
        plain_num += 1
        # multiprocessing.Value modifications are
        shared_num.value += 1

ps = [multiprocessing.Process(target=increment) for n in range(4)]
for p in ps:
    p.start()
for p in ps:
    p.join()

print("plain_num is %d, shared_num is %d" % (plain_num, shared_num.value))

# Out: plain_num is 0, shared_num is 4



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow