私が書いたいくつかのコードにとても戸惑っていました。私はそれを発見して驚きました:
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))
異なる結果を生み出します。 1つ目は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
を使用します。そうするのは間違っていますか?
問題は、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
よりも時間がかかる可能性があり、これが実際に当てはまる場合、mapの簡単な使用を維持しながらパフォーマンスの大きな損失ではありません。
また、ワーカースレッド(/プロセス)はリストの先頭から処理を開始し、送信したリストの最後まで処理するため、結果はイテレータによって既に生成された順序で完了する必要があります。つまり、ほとんどの場合executor.map
は問題ありませんが、たとえば、値を処理する順序とmap
に渡した関数の処理順序が重要でない場合など、時間がかかる場合があります実行するには、future.as_completed
の方が高速です。
以下は、送信とマップの例です。両方ともすぐにジョブを受け入れます(submitted | mapped-start)。それらは完了するのに同じ時間、11秒かかります(最後の結果時間-開始)。ただし、submitは、ThreadPoolExecutor maxThreads = 2のスレッドが完了するとすぐに結果を出します。マップは、送信された順序で結果を提供します。
import time
import concurrent.futures
def worker(i):
time.sleep(i)
return i,time.time()
e = concurrent.futures.ThreadPoolExecutor(2)
arrIn = range(1,7)[::-1]
print arrIn
f = []
print 'start submit',time.time()
for i in arrIn:
f.append(e.submit(worker,i))
print 'submitted',time.time()
for r in concurrent.futures.as_completed(f):
print r.result(),time.time()
print
f = []
print 'start map',time.time()
f = e.map(worker,arrIn)
print 'mapped',time.time()
for r in f:
print r,time.time()
出力:
[6, 5, 4, 3, 2, 1]
start submit 1543473934.47
submitted 1543473934.47
(5, 1543473939.473743) 1543473939.47
(6, 1543473940.471591) 1543473940.47
(3, 1543473943.473639) 1543473943.47
(4, 1543473943.474192) 1543473943.47
(1, 1543473944.474617) 1543473944.47
(2, 1543473945.477609) 1543473945.48
start map 1543473945.48
mapped 1543473945.48
(6, 1543473951.483908) 1543473951.48
(5, 1543473950.484109) 1543473951.48
(4, 1543473954.48858) 1543473954.49
(3, 1543473954.488384) 1543473954.49
(2, 1543473956.493789) 1543473956.49
(1, 1543473955.493888) 1543473956.49
ここの回答の説明に加えて、ソースに直接アクセスすると役立つ場合があります。ここでの別の答えからの声明を再確認します:
.map()
は、送信された順序で結果を提供しますが、concurrent.futures.as_completed()
を使用してFuture
オブジェクトのリストを繰り返し処理しても、この順序は保証されません。 as_completed()
.map()
は基本クラスで定義されています _concurrent.futures._base.Executor
_ :
_class Executor(object):
def submit(self, fn, *args, **kwargs):
raise NotImplementedError()
def map(self, fn, *iterables, timeout=None, chunksize=1):
if timeout is not None:
end_time = timeout + time.monotonic()
fs = [self.submit(fn, *args) for args in Zip(*iterables)] # <!!!!!!!!
def result_iterator():
try:
# reverse to keep finishing order
fs.reverse() # <!!!!!!!!
while fs:
# Careful not to keep a reference to the popped future
if timeout is None:
yield fs.pop().result() # <!!!!!!!!
else:
yield fs.pop().result(end_time - time.monotonic())
finally:
for future in fs:
future.cancel()
return result_iterator()
_
言及したように、.submit()
もあります。これは、子クラス、つまりProcessPoolExecutor
と ThreadPoolExecutor
、そして実際に何かをするために.result()
を呼び出す必要がある__base.Future
_インスタンスを返します。
.map()
の重要な行は次のように要約されます。
_fs = [self.submit(fn, *args) for args in Zip(*iterables)]
fs.reverse()
while fs:
yield fs.pop().result()
_
.reverse()
と.pop()
は、最初に送信された結果(iterables
から)を最初に取得し、2番目に送信された結果を2番目に取得する手段です。オン。結果のイテレータの要素はFuture
sではありません。それらは実際の結果そのものです。