Python 捕获多进程 OR 多线程 error 的最佳实现是什么?

2020-01-07 14:50:42 +08:00
 wuwukai007
def test():
    a=[2]
    print(a[3])

def run():
    test()

def err(error):
    logger.exception(error)

if __name__ == '__main__':
    pool = Pool(2)
    t1 = pool.apply_async(run,error_callback=err)
    pool.close()
    pool.join()

日志记录 :

list index out of range
def test():
    a=[2]
    print(a[3])

def run():
    test()

def err(error):
    #这边已经拿到 error 了但是还要 try,要不然日志只记录 list index out of range
    try:
        raise error
    except Exception as e:
        logger.exception(e)

if __name__ == '__main__':
    pool = Pool(2)
    t1 = pool.apply_async(run,error_callback=err)
    pool.close()
    pool.join()

日志输出 :

list index out of range
multiprocessing.pool.RemoteTraceback: 
"""
Traceback (most recent call last):
  File "/usr/lib/python3.5/multiprocessing/pool.py", line 119, in worker
    result = (True, func(*args, **kwds))
  File "/home/work_dir/test/timer.py", line 13, in run
    test()
  File "/home/work_dir/test/timer.py", line 10, in test
    print(a[3])
IndexError: list index out of range
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/work_dir/test/timer.py", line 17, in err
    raise error
IndexError: list index out of range

难道要在回调函数里再次捕获输出?

2643 次点击
所在节点    Python
10 条回复
BingoXuan
2020-01-07 14:58:58 +08:00
多进程和多线程通讯的问题,内置有 queue 之类的数据类型可以使用。
wuwukai007
2020-01-07 15:01:43 +08:00
@BingoXuan 不是通信,是抓到子进程子线程错误,输出到日志或者写状态等,但是拿不到堆栈信息。我现在是再次 try 或者是用装饰器来做
zyqzyq08
2020-01-07 15:07:23 +08:00
可以考虑直接重写 sys.stderr
BingoXuan
2020-01-07 15:17:04 +08:00
你试图捕抓子进程或子线程信息本质就是通讯。为什么一定要主进程去干着活呢?你完全可以让子进程或子线程运行时候去捕捉啊。装饰器的其中一个广泛用途就是捕获并处理错误信息。如果子进程是一个 worker,那么子进程 worker 接受主进程的请求运行指定函数,记录运行结果或捕获运行错误,并通过 socket 或文件通知或保存信息。
Vegetable
2020-01-07 15:18:59 +08:00
traceback.format_exc()是你想要的?
oahebky
2020-01-07 15:19:26 +08:00
了解一下 traceback 库或许你就有答案了。
wuwukai007
2020-01-07 15:22:47 +08:00
@Vegetable traceback.format_exc() 是主进程中错误会抛出,用的进程池或线程池,除非用装饰器包起来,要不然子进程错误不会抛出来的,比如我现在用的回调,用这个直接输出 NoneType
todd7zhang
2020-01-07 17:42:25 +08:00
子进程的错误, 就让子进程抛呗
wuwukai007
2020-01-07 17:49:30 +08:00
@todd7zhang 子进程要捕获,之前用的装饰器把子进程整个包起来可以拿到 error,但是这样感觉很浪费,应为进程池本身就能拿到 error,这样多此一举了
hiyoi
2020-05-19 16:29:07 +08:00
最近刚好碰到这个情况,我是这么解决的,把要子线程和日志对象封装到一起,使用 logger.exception()来 traceback 错误


```
from concurrent.futures import ThreadPoolExecutor
import logging


class Main(object):
def __init__(self, logger=None):
self.pool = ThreadPoolExecutor
self.logger = logger or self._get_logger(__name__)

def _get_logger(self, name):
logger = logging.getLogger(name)
handler = logging.StreamHandler()
formatter = logging.Formatter(
"%(asctime)s - %(levelname)s - %(threadName)s - %(message)s")
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
return logger

def do_something(self):
try:
a = 1 / 0
except Exception as e:
self.logger.exception(e)

def async_task(self):
with self.pool() as p:
for i in range(3):
p.submit(self.do_something)


if __name__ == '__main__':
m = Main()
m.async_task()
```

日志:
```
2020-05-19 16:23:38,378 - ERROR - ThreadPoolExecutor-0_0 - division by zero
Traceback (most recent call last):
File "thread.py", line 22, in do_something
a = 1 / 0
ZeroDivisionError: division by zero
2020-05-19 16:23:38,378 - ERROR - ThreadPoolExecutor-0_0 - division by zero
Traceback (most recent call last):
File "thread.py", line 22, in do_something
a = 1 / 0
ZeroDivisionError: division by zero
2020-05-19 16:23:38,378 - ERROR - ThreadPoolExecutor-0_2 - division by zero
Traceback (most recent call last):
File "thread.py", line 22, in do_something
a = 1 / 0
ZeroDivisionError: division by zero
```

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/635803

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX