93

私が書いたいくつかのコードに非常に混乱していました。私は次のことを発見して驚いた:

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(f, iterable))

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
    results = list(map(lambda x: executor.submit(f, x), iterable))

さまざまな結果を生み出します。最初のものは、f返される型のリストを生成し、2 番目のものは、返された値を取得するためにメソッドでconcurrent.futures.Future評価する必要があるオブジェクトのリストを生成します。result()f

私の主な懸念は、これは が をexecutor.map利用できないことを意味するということです。これconcurrent.futures.as_completedは、利用可能になったときに実行しているデータベースへの長時間の呼び出しの結果を評価する非常に便利な方法のように思えます。

オブジェクトがどのように機能するかについてはまったく明確ではありませんconcurrent.futures.ThreadPoolExecutor-単純に、(やや冗長)を好むでしょう:

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
    result_futures = list(map(lambda x: executor.submit(f, x), iterable))
    results = [f.result() for f in futures.as_completed(result_futures)]

executor.mapパフォーマンスが向上する可能性を利用するために、より簡潔にします。そうするのは間違っていますか?

4

4 に答える 4

51

ThreadPoolExecutor.map問題は、結果をリストに変換することです。これを行わず、代わりに結果のジェネレーターを直接反復すると、結果は元の順序で生成されますが、すべての結果が準備される前にループが続行されます。次の例でこれをテストできます。

import time
import concurrent.futures

e = concurrent.futures.ThreadPoolExecutor(4)
s = range(10)
for i in e.map(time.sleep, s):
    print(i)

順序が維持される理由は、マップに指定した順序と同じ順序で結果を取得することが重要な場合があるためです。また、状況によっては、必要な場合にすべての結果を取得するためにリストに対して別のマップを実行するのに時間がかかりすぎるため、結果は将来のオブジェクトにラップされない可能性があります。結局のところ、ほとんどの場合、ループが最初の値を処理する前に次の値の準備ができている可能性が非常に高いです。これは、次の例で示されています。

import concurrent.futures

executor = concurrent.futures.ThreadPoolExecutor() # Or ProcessPoolExecutor
data = some_huge_list()
results = executor.map(crunch_number, data)
finals = []

for value in results:
    finals.append(do_some_stuff(value))

do_some_stuffこの例では、これよりも時間がかかる可能性がありますcrunch_number。実際にそうである場合、マップの簡単な使用法を維持しながら、パフォーマンスが大幅に低下することはありません。

また、ワーカー スレッド (/プロセス) はリストの先頭から処理を開始し、送信したリストの最後まで処理を進めるため、結果はイテレータによって既に生成された順序で終了する必要があります。つまり、ほとんどの場合executor.mapは問題ありませんが、たとえば、値を処理する順序が問題ではなく、渡した関数のmap実行に非常に異なる時間がかかるfuture.as_completed場合は、の方が高速になる可能性があります。

于 2013-12-30T23:03:58.403 に答える