消费者线程中的索引超出范围

时间:2013-12-10 18:21:34

标签: python multithreading synchronization producer-consumer

我有一个Thread应该等待任务从不同的多线程到达并执行它们直到没有剩下任务。如果没有剩下任务,它应该再次等待。

我尝试了这个类(只有相关的代码):

from threading import Event, Thread

class TaskExecutor(object):
    def __init__(self):
        self.event = Event()
        self.taskInfos = []
        self._running = True

        task_thread = Thread(target=self._run_worker_thread)
        self._running = True
        task_thread.daemon = True
        task_thread.start()

    def _run_worker_thread(self):
        while self.is_running():
            if len(self.taskInfos) == 0:
                self.event.clear()
                self.event.wait()

            try:
                msg, task = self.taskInfos[0]
                del self.taskInfos[0]
                if task:
                    task.execute(msg)
            except Exception, e:
                logger.error("Error " + str(e))

    def schedule_task(self, msg, task):
        self.taskInfos.append((msg, task))
        self.event.set()

多个线程在每次添加任务时都在调用schedule_task

问题是我有时会在list index out of range行中发出错误msg, task = self.taskInfos[0]。下面的del self.taskInfos[0]是我删除任务的唯一选项。

怎么会发生这种情况?我觉得我必须synchronize所有内容,但python中没有这样的关键字,阅读文档会带来这种模式。

2 个答案:

答案 0 :(得分:3)

这段代码非常无望 - 放弃它并做一些理智的事情;-)什么是理智的?使用Queue.Queue。那是为了做你想做的事。

替换:

    self.event = Event()
    self.taskInfos = []

使用:

    self.taskInfos = Queue.Queue()

(当然你也必须import Queue。)

添加任务:

    self.taskInfos.put((msg, task))

要完成任务:

    msg, task = self.taskInfos.get()

这将阻止任务可用。还可以选择执行非阻塞.get() 尝试,并在超时时执行.get()尝试(请阅读文档)。

尝试修复您拥有的代码将是一场噩梦。从本质上讲,Event的功能不足以在此上下文中执行线程安全所需的操作。事实上,每当你看到代码正在执行Event.clear()时,它的可能错误(受比赛影响)。

编辑:接下来会出现什么问题

如果您继续尝试修复此代码,则可能会发生以下情况:

the queue is empty
thread 1 does len(self.taskInfo) == 0, and loses its timeslice
thread 2 does self.taskInfos.append((msg, task))
         and does self.event.set()
         and loses its timeslice
thread 1 resumes and does self.event.clear()
         and does self.event.wait()

糟糕!现在线程1永远等待,尽管队列中的任务

这就是Python提供Queue.Queue的原因。使用虚弱的Event,你极不可能得到正确的解决方案。

答案 1 :(得分:2)

可以使用以下序列(假设线程#0 是使用者并运行您的_run_worker_thread方法,并且线程线程#1 线程#2 是生产者并调用schedule_task方法):

  • 线程#0等待事件,队列为空
  • 主题#1调用schedule_taskset
  • 之前被抢占
  • 线程#2调用schedule_task并到达set
  • 线程#0唤醒并执行两次迭代,清除任务队列
  • 线程#1唤醒并到达set电话
  • 线程#0以错误状态唤醒 - 队列为空

粗体部分是理解可能的种族的关键。基本上,工作线程可以使用所有任务,在if len(self.taskInfos) == 0条件为False之前旋转,然后所有生成器在追加到队列后将设置为set事件。

可能的解决方案包括wait之后根据xndrme的评论中的建议再次检查条件,或者使用Lock类,最好的一个可能是提到的Queue.QueueTim Peters中的his answer