使用 ThreadPool 时遇到的奇怪错误

87 阅读2分钟

在 Django 应用中实现一个简单的网络任务时,发现只有第一次运行时代码能够正常工作,之后的运行会返回以下错误:

Traceback:
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response
  111.                         response = callback(request, *callback_args, **callback_kwargs)
File "/var/www/propingui/ping/views.py" in ping
  32.             p = Pool(len(fls))
File "/usr/lib/python2.7/multiprocessing/pool.py" in __init__
  674.         Pool.__init__(self, processes, initializer, initargs)
File "/usr/lib/python2.7/multiprocessing/pool.py" in __init__
  134.         self._repopulate_pool()
File "/usr/lib/python2.7/multiprocessing/pool.py" in _repopulate_pool
  197.             w.start()
File "/usr/lib/python2.7/multiprocessing/dummy/__init__.py" in start
  73.         self._parent._children[self] = None

Exception Type: AttributeError at /
Exception Value: '_DummyThread' object has no attribute '_children'

经过调查发现,这个错误与在 Django 中使用 ThreadPool 有关。

2、解决方案: 为了解决这个问题,有以下几种解决方案:

  • 使用 threading 模块:

    • 将 ThreadPool 替换为 threading 模块,使用 threading 模块中的 Thread 类来创建线程。这种方法可以在 Django 中正常运行,但性能可能不如 ThreadPool。
    • 代码示例:
    import threading
    
    def _ping((host, firing_location)):
        pinger = Pyro4.Proxy("PYRONAME:" + firing_location)
        return pinger.ping(host)
    
    def ping(request):
        if request.method == 'POST':
            form = PingForm(request.POST)
            if form.is_valid():
                host = form.cleaned_data['host']
                fls = ['g1','a1']
                jobs = []
                for fl in fls:
                    t = threading.Thread(target=_ping, args=((host, fl),))
                    t.start()
                    jobs.append(t)
            ...
            return ...
    
  • 使用 patch 修复:

    • 因为这个错误是 Python 的一个已知问题,所以可以应用 patch 来修复它。
    • 代码示例:
    import multiprocessing.dummy as multiprocessing
    
    if hasattr(multiprocessing.current_thread(), "_children"):
        multiprocessing.current_thread()._children = weakref.WeakKeyDictionary()
    
  • 在 Pyro 服务中分发 ping 任务:

    • 另一种解决方法是创建一个 Pyro 服务,该服务将 ping 任务分发到所有服务器。这种方法可以避免在 Django 中使用线程或 fork,从而绕过这个问题。
    • 代码示例:
    import Pyro4
    
    @Pyro4.expose
    class PingService:
        def ping(self, host):
            # Perform the ping operation here
    
    daemon = Pyro4.Daemon()
    uri = daemon.register(PingService)
    
    # Start the Pyro daemon
    daemon.requestLoop()
    
  • 使用自定义 ThreadPool:

    from concurrent.futures import ThreadPoolExecutor
    
    def _ping((host, firing_location)):
        pinger = Pyro4.Proxy("PYRONAME:" + firing_location)
        return pinger.ping(host)
    
    def ping(request):
        if request.method == 'POST':
            form = PingForm(request.POST)
            if form.is_valid():
                host = form.cleaned_data['host']
                fls = ['g1','a1']
                with ThreadPoolExecutor(max_workers=len(fls)) as executor:
                    jobs = executor.map(_ping, zip([host]*len(fls), fls))
            ...
            return ...