【问题标题】:Stopping processes in ThreadPool in Python在 Python 中的 ThreadPool 中停止进程
【发布时间】:2016-12-15 21:24:01
【问题描述】:

我一直在尝试为控制某些硬件的库编写交互式包装器(用于 ipython)。一些调用在 IO 上很重,因此并行执行任务是有意义的。使用线程池(几乎)效果很好:

from multiprocessing.pool import ThreadPool

class hardware():
    def __init__(IPaddress):
        connect_to_hardware(IPaddress)

    def some_long_task_to_hardware(wtime):
        wait(wtime)
        result = 'blah'
        return result

pool = ThreadPool(processes=4)
Threads=[]
h=[hardware(IP1),hardware(IP2),hardware(IP3),hardware(IP4)]
for tt in range(4):
    task=pool.apply_async(h[tt].some_long_task_to_hardware,(1000))
    threads.append(task)
alive = [True]*4
Try:
    while any(alive) :
        for tt in range(4): alive[tt] = not threads[tt].ready()
        do_other_stuff_for_a_bit()
except:
    #some command I cannot find that will stop the threads...
    raise
for tt in range(4): print(threads[tt].get())

如果用户想要停止进程或者do_other_stuff_for_a_bit() 中存在 IO 错误,就会出现问题。按 Ctrl+C 会停止主进程,但工作线程会继续运行,直到当前任务完成。
有什么方法可以停止这些线程而不必重写库或让用户退出 python?我在其他示例中看到的pool.terminate()pool.join() 似乎无法完成这项工作。

实际的例程(而不是上面的简化版本)使用日志记录,尽管所有工作线程都在某个时候关闭,但我可以看到他们开始运行的进程一直持续到完成(作为硬件,我可以看到他们的透过房间看的效果)。

这是在 python 2.7 中。

更新:

解决方案似乎是改用 multiprocessing.Process 而不是线程池。我尝试的测试代码是运行 foo_pulse:

class foo(object):
    def foo_pulse(self,nPulse,name): #just one method of *many*
        print('starting pulse for '+name)
        result=[]
        for ii in range(nPulse):
            print('on for '+name)
            time.sleep(2)
            print('off for '+name)
            time.sleep(2)
            result.append(ii)
        return result,name

如果您尝试使用 ThreadPool 运行它,那么 ctrl-C 不会阻止 foo_pulse 运行(即使它会立即终止线程,打印语句也会继续出现:

from multiprocessing.pool import ThreadPool
import time
def test(nPulse):
    a=foo()
    pool=ThreadPool(processes=4)
    threads=[]
    for rn in range(4) :
        r=pool.apply_async(a.foo_pulse,(nPulse,'loop '+str(rn)))
        threads.append(r)
    alive=[True]*4
    try:
        while any(alive) : #wait until all threads complete
            for rn in range(4):
                alive[rn] = not threads[rn].ready() 
                time.sleep(1)
    except : #stop threads if user presses ctrl-c
        print('trying to stop threads')
        pool.terminate()
        print('stopped threads') # this line prints but output from foo_pulse carried on.
        raise
    else : 
        for t in threads : print(t.get())

但是使用 multiprocessing.Process 的版本按预期工作:

import multiprocessing as mp
import time
def test_pro(nPulse):
    pros=[]
    ans=[]
    a=foo()
    for rn in range(4) :
        q=mp.Queue()
        ans.append(q)
        r=mp.Process(target=wrapper,args=(a,"foo_pulse",q),kwargs={'args':(nPulse,'loop '+str(rn))})
        r.start()
        pros.append(r)
    try:
        for p in pros : p.join()
        print('all done')
    except : #stop threads if user stops findRes
        print('trying to stop threads')
        for p in pros : p.terminate()
        print('stopped threads')
    else : 
        print('output here')
        for q in ans :
            print(q.get())
    print('exit time')

我已经为库 foo 定义了一个包装器(因此它不需要重新编写)。如果不需要返回值,则此包装器也不是:

def wrapper(a,target,q,args=(),kwargs={}):
    '''Used when return value is wanted'''
    q.put(getattr(a,target)(*args,**kwargs))

从文档中我看不出为什么池不起作用(除了错误)。

【问题讨论】:

标签: python threadpool


【解决方案1】:

这是并行性的一个非常有趣的用法。

但是,如果您使用multiprocessing,目标是让多个进程并行运行,而不是一个进程运行多个线程。

考虑这些更改以使用multiprocessing 实现它:

你有这些并行运行的函数:

import time
import multiprocessing as mp


def some_long_task_from_library(wtime):
    time.sleep(wtime)


class MyException(Exception): pass

def do_other_stuff_for_a_bit():
    time.sleep(5)
    raise MyException("Something Happened...")

让我们创建并启动进程,比如 4:

procs = []  # this is not a Pool, it is just a way to handle the
            # processes instead of calling them p1, p2, p3, p4...
for _ in range(4):
    p = mp.Process(target=some_long_task_from_library, args=(1000,))
    p.start()
    procs.append(p)
mp.active_children()   # this joins all the started processes, and runs them.

进程并行运行,大概在一个单独的 cpu 内核中,但这是由操作系统决定的。您可以检查您的系统监视器。

与此同时,您运行的进程会中断,并且您希望停止正在运行的进程,而不是让它们成为孤儿:

try:
    do_other_stuff_for_a_bit()
except MyException as exc:
    print(exc)
    print("Now stopping all processes...")
    for p in procs:
        p.terminate()
print("The rest of the process will continue")

如果在一个或所有子进程终止后继续主进程没有意义,则应处理主程序的退出。

希望它有所帮助,您可以将其中的一些内容用于您的图书馆。

【讨论】:

  • 在我的情况下,如果一切都在同一个 CPU 上运行并不重要,并行运行的原因是 IO 上有大量等待。然而,这种方法有一个缺点,即很难从调用中返回值。现在我用包装函数解决了这个问题 - 请参阅我更新的帖子。
  • 根据需要从调用中返回的值,您可以使用QueuePipe、共享内存ValueArray,甚至是磁盘文件。在某些情况下,您可能需要使用Locks。
【解决方案2】:

回答为什么池不起作用的问题是由于(如Documentation 中所引用)然后 ma​​in 需要由子进程导入并且由于性质这个项目的交互式 python 正在使用中。

与此同时,尚不清楚为什么 ThreadPool 会 - 尽管线索就在名称中。 ThreadPool 使用 multiprocessing.dummy 创建其工作进程池,如前所述 here 只是 Threading 模块的包装器。池使用 multiprocessing.Process。通过这个测试可以看出:

p=ThreadPool(processes=3)
p._pool[0]
<DummyProcess(Thread23, started daemon 12345)> #no terminate() method

p=Pool(processes=3)
p._pool[0]
<Process(PoolWorker-1, started daemon)> #has handy terminate() method if needed

由于线程没有终止方法,因此工作线程会继续运行,直到完成当前任务。杀死线程很麻烦(这就是我尝试使用多处理模块的原因)但解决方案是here

关于使用上述解决方案的一个警告:

def wrapper(a,target,q,args=(),kwargs={}):
    '''Used when return value is wanted'''
    q.put(getattr(a,target)(*args,**kwargs))

是对象实例内部属性的更改不会传递回主程序。例如,上面的类 foo 也可以具有以下方法: 定义添加IP(新IP): self.hardwareIP=newIP 对r=mp.Process(target=a.addIP,args=(127.0.0.1)) 的调用不会更新a

对于复杂对象,唯一的解决方法似乎是使用自定义manager 共享内存,它可以访问对象a 的方法和属性对于基于库的非常大的复杂对象,这可能最好使用dir(foo) 填充管理器。如果我能弄清楚如何用一个例子来更新这个答案(对于我未来的自己和其他人一样)。

【讨论】:

    【解决方案3】:

    如果出于某些原因使用线程更可取,我们可以使用this

    我们可以向我们想要终止的线程发送一些信号。最简单的信号是全局变量:

    import time
    from multiprocessing.pool import ThreadPool
    
    _FINISH = False
    
    def hang():
        while True:
            if _FINISH:
                break
            print 'hanging..'
            time.sleep(10)
    
    
    def main():
        global _FINISH
        pool = ThreadPool(processes=1)
        pool.apply_async(hang)
        time.sleep(10)
        _FINISH = True
        pool.terminate()
        pool.join()
        print 'main process exiting..'
    
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

      猜你喜欢
      • 2014-08-12
      • 2020-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多