web-dev-qa-db-ja.com

Python:concurrent.futuresキャンセル可能にする方法は?

Python concurrent.futuresとProcessPoolExecutorは、タスクをスケジュールおよび監視するための優れたインターフェースを提供します。先物も 提供 .cancel()メソッド:

cancel():通話のキャンセルを試みます。呼び出しが現在実行中でキャンセルできない場合の場合、メソッドはFalseを返します。それ以外の場合、呼び出しはキャンセルされ、メソッドはTrueを返します。

残念ながら、似たような question (asyncioに関して)回答は、このドキュメントの一部を使用して実行中のタスクをキャンセルできないと主張していますが、ドキュメントは、実行中であり、キャンセルできない場合にのみ、そのように述べていません。

プロセスにmultiprocessing.Eventsを送信することも簡単にはできません(multiprocess.Processのようにパラメーターを介して実行すると、RuntimeErrorが返されます)。

私は何をしようとしていますか?サーチスペースを分割し、すべてのパーティションに対してタスクを実行したいと思います。しかし、1つのソリューションで十分であり、プロセスはCPUを集中的に使用します。それで、最初にProcessPoolを使用して利益を相殺しない、これを実現する実際の快適な方法はありますか?

例:

from concurrent.futures import ProcessPoolExecutor, FIRST_COMPLETED, wait

# function that profits from partitioned search space
def m_run(partition):
    for elem in partition:
        if elem == 135135515:
            return elem
    return False

futures = []
# used to create the partitions
steps = 100000000
with ProcessPoolExecutor(max_workers=4) as pool:
    for i in range(4):
        # run 4 tasks with a partition, but only *one* solution is needed
        partition = range(i*steps,(i+1)*steps)
        futures.append(pool.submit(m_run, partition))

    done, not_done = wait(futures, return_when=FIRST_COMPLETED)
    for d in done:
        print(d.result())

    print("---")
    for d in not_done:
        # will return false for Cancel and Result for all futures
        print("Cancel: "+str(d.cancel()))
        print("Result: "+str(d.result()))
15
Ketzu

_concurrent.futures.Future_に.kill()メソッドがない理由はわかりませんが、pool.shutdown(wait=False)を使用してプロセスプールをシャットダウンし、残りを強制終了することで、必要なことを実行できます子は手動で処理します。

子プロセスを強制終了するための関数を作成します。

_import signal, psutil

def kill_child_processes(parent_pid, sig=signal.SIGTERM):
    try:
        parent = psutil.Process(parent_pid)
    except psutil.NoSuchProcess:
        return
    children = parent.children(recursive=True)
    for process in children:
        process.send_signal(sig)
_

最初の結果が得られるまでコードを実行し、残りのすべての子プロセスを強制終了します。

_from concurrent.futures import ProcessPoolExecutor, FIRST_COMPLETED, wait

# function that profits from partitioned search space
def m_run(partition):
    for elem in partition:
        if elem == 135135515:
            return elem
    return False

futures = []
# used to create the partitions
steps = 100000000
pool = ProcessPoolExecutor(max_workers=4)
for i in range(4):
    # run 4 tasks with a partition, but only *one* solution is needed
    partition = range(i*steps,(i+1)*steps)
    futures.append(pool.submit(m_run, partition))

done, not_done = wait(futures, timeout=3600, return_when=FIRST_COMPLETED)

# Shut down pool
pool.shutdown(wait=False)

# Kill remaining child processes
kill_child_processes(os.getpid())
_
7
ostrokach

残念ながら、Futuresの実行はキャンセルできません。中心的な理由は、異なるAPIで同じAPIを保証することです(実行中のスレッドやコルーチンを中断することはできません)。

Pebble ライブラリは、この制限やその他の制限を克服するように設計されています。

from pebble import ProcessPool

def function(foo, bar=0):
    return foo + bar

with ProcessPool() as pool:
    future = pool.schedule(function, args=[1])

    # if running, the container process will be terminated 
    # a new process will be started consuming the next task
    future.cancel()  
7
noxdafox

私はあなたの質問が興味深いので、ここに私の発見があります。

.cancel()メソッドの動作はpythonのドキュメントに記載されているとおりです。実行中の並行関数に関しては、残念ながら、そうするように言われた後でもキャンセルできませんでした。私の発見が正しければ、Pythonはより効果的な.cancel()メソッドを必要とするためです。

以下のコードを実行して、私の発見を確認してください。

from concurrent.futures import ProcessPoolExecutor, as_completed
from time import time 

# function that profits from partitioned search space
def m_run(partition):
    for elem in partition:
        if elem == 3351355150:
            return elem
            break #Added to terminate loop once found
    return False

start = time()
futures = []
# used to create the partitions
steps = 1000000000
with ProcessPoolExecutor(max_workers=4) as pool:
    for i in range(4):
        # run 4 tasks with a partition, but only *one* solution is needed
        partition = range(i*steps,(i+1)*steps)
        futures.append(pool.submit(m_run, partition))

    ### New Code: Start ### 
    for f in as_completed(futures):
        print(f.result())
        if f.result():
            print('break')
            break

    for f in futures:
        print(f, 'running?',f.running())
        if f.running():
            f.cancel()
            print('Cancelled? ',f.cancelled())

    print('New Instruction Ended at = ', time()-start )
print('Total Compute Time = ', time()-start )

pdate: bashを使用して並行プロセスを強制終了することは可能ですが、その結果、メインのpythonプログラムも終了します。これが問題ではない場合あなたと一緒に、次に以下のコードを試してください。

これを確認するには、最後の2つの印刷ステートメントの間に以下のコードを追加する必要があります。注:このコードは、他のpython3プログラムを実行していない場合にのみ機能します。

import subprocess, os, signal 
result = subprocess.run(['ps', '-C', 'python3', '-o', 'pid='],
                        stdout=subprocess.PIPE).stdout.decode('utf-8').split()
print ('result =', result)
for i in result:
    print('PID = ', i)
    if i != result[0]:
        os.kill(int(i), signal.SIGKILL)
        try: 
           os.kill(int(i), 0)
           raise Exception("""wasn't able to kill the process 
                              HINT:use signal.SIGKILL or signal.SIGABORT""")
        except OSError as ex:
           continue
2
Sun Bear