Python线程:我缺少什么? (task_done()调用次数太多)

时间:2011-09-16 01:59:02

标签: python multithreading queue

我为前面的长篇大论道歉。希望它能为解决方案提供足够的背景信息。我试图创建一个实用程序函数,它将使用任意数量的旧classmethod并将它们粘贴到多线程队列中:

class QueuedCall(threading.Thread):

    def __init__(self, name, queue, fn, args, cb):
        threading.Thread.__init__(self)
        self.name = name

        self._cb = cb
        self._fn = fn
        self._queue = queue
        self._args = args

        self.daemon = True
        self.start()

    def run(self):
        r = self._fn(*self._args) if self._args is not None \
            else self._fn()

        if self._cb is not None:
            self._cb(self.name, r)

            self._queue.task_done()

这是我的调用代码的样子(在一个类中)

data = {}
def __op_complete(name, r):
    data[name] = r

q = Queue.Queue()

socket.setdefaulttimeout(5)

q.put(QueuedCall('twitter', q, Twitter.get_status, [5,], __op_complete))
q.put(QueuedCall('so_answers', q, StackExchange.get_answers,
    ['api.stackoverflow.com', 534476, 5], __op_complete))
q.put(QueuedCall('so_user', q, StackExchange.get_user_info,
    ['api.stackoverflow.com', 534476], __op_complete))
q.put(QueuedCall('p_answers', q, StackExchange.get_answers,
    ['api.programmers.stackexchange.com', 23901, 5], __op_complete))
q.put(QueuedCall('p_user', q, StackExchange.get_user_info,
    ['api.programmers.stackexchange.com', 23901], __op_complete))
q.put(QueuedCall('fb_image', q, Facebook.get_latest_picture, None, __op_complete))

q.join()
return data

我遇到的问题是,它似乎在新服务器重新启动时时间工作,但每隔一秒或第三次请求失败,并出现错误:

ValueError: task_done() called too many times

此错误在每个第二或第三个请求的随机线程中出现,因此很难确定完全问题是什么。

任何人都有任何想法和/或建议吗?

感谢。


修改

我已经添加了print以试图调试它(快速而肮脏而不是记录)。在print 'running thread: %s' % self.name的第一行中有一个打印语句(run),在调用task_done()print 'thread done: %s' % self.name)之前另一个打印语句。

成功请求的输出:

running thread: twitter
running thread: so_answers
running thread: so_user
running thread: p_answers
thread done: twitter
thread done: so_user
running thread: p_user
thread done: so_answers
running thread: fb_image
thread done: p_answers
thread done: p_user
thread done: fb_image

请求失败的输出:

running thread: twitter
running thread: so_answers
thread done: twitter
thread done: so_answers
running thread: so_user
thread done: so_user
running thread: p_answers
thread done: p_answers
Exception in thread p_answers:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/home/demian/src/www/projects/demianbrecht/demianbrecht/demianbrecht/helpers.py", line 37, in run
    self._queue.task_done()
  File "/usr/lib/python2.7/Queue.py", line 64, in task_done
    raise ValueError('task_done() called too many times')
ValueError: task_done() called too many times

running thread: p_user
thread done: p_user
running thread: fb_image
thread done: fb_image

2 个答案:

答案 0 :(得分:6)

你解决这个问题的方法是“非常规的”。但是现在忽略了这个问题......问题只是在你给出的代码中

q.put(QueuedCall('twitter', q, Twitter.get_status, [5,], __op_complete))

显然可以进行以下工作流程

  1. 线程由QueuedCall .__ init __
  2. 构建并启动
  3. 然后将其放入队列q中。但是......在Queue完成其插入项的逻辑之前,独立线程已经完成其工作并尝试调用q.task_done()。这会导致您的错误(在对象安全地放入队列之前调用了task_done())
  4. 应该如何做?您不会将线程插入队列。队列保存线程处理的数据。所以相反,你

    • 创建队列。在其中插入您想要完成的工作(例如,功能,他们想要的参数和回调)
    • 您创建并启动工作线程
    • 工作线程调用
      • q.get()获取调用函数
      • 调用它
      • 调用q.task_done()让队列知道项目已被处理。

答案 1 :(得分:2)

我可能在这里误会,但我不确定你是否正确使用Queue

通过对文档的简短调查,看起来您的想法是您可以使用put方法将工作放入Queue,然后另一个线程可以调用get来从中完成一些工作,完成工作,然后在完成后调用task_done

您的代码似乎在putQueuedCall个实例进入队列。队列中没有get,但QueuedCall个实例也被传递给他们被插入的队列的引用,并且他们完成了他们的工作(他们知道本质上,不是因为他们从队列中get然后调用task_done

如果我对所有这些内容的解读是正确的(并且你没有从其他地方调用get方法我看不到),那么我相信我理解了这个问题。

问题是必须在将QueuedCall实例放入队列之前创建它们,并且创建一个实例的行为在另一个线程中开始工作。如果线程完成其工作并在之前调用task_done ,主线程已设法put QueuedCall进入队列,那么您可以看到错误

我认为它只会在你第一次意外运行时才有效。 GIL'帮助'你很多; QueuedCall线程不太可能实际获得GIL并立即开始运行。除了作为计数器之外你实际上并不关心队列这一事实也“帮助”这似乎有效:如果QueuedCall尚未到达队列,只要它不是为空(此QueuedCall只能task_done队列中的另一个元素,并且当 元素调用task_done时,这个元素有望在队列中,并且它可以被标记为完成)。并且添加sleep也会使新线程稍微等待,给主线程时间以确保它们实际上在队列中,这也是掩盖问题的原因。

另外请注意,据我所知,从一些快速摆弄交互式shell的过程中,你的队列实际上仍然是完整的,因为你实际上从来没有get任何东西。它刚收到一些task_done条消息,其中包含put内的join条消息,因此QueuedCall有效。

我认为您需要从根本上重新设计Queue类的工作方式,或使用与Queue不同的同步原语。 {{1}}旨在用于为已存在的工作线程排队工作。从构造函数中为您放入队列的对象启动一个线程并不是很合适。