【问题标题】:How check if a task is already in python Queue?如何检查任务是否已经在 python 队列中?
【发布时间】:2010-12-07 14:27:52
【问题描述】:

我正在使用 Python 中的 threading 和 Queue 模块编写一个简单的爬虫。我获取一个页面,检查链接并将它们放入队列中,当某个线程完成处理页面时,它会从队列中获取下一个。我正在为我已经访问过的页面使用一个数组来过滤我添加到队列中的链接,但是如果有多个线程并且它们在不同的页面上获得相同的链接,它们会将重复的链接放入队列中。那么如何确定某个 url 是否已经在队列中以避免再次将其放在那里呢?

【问题讨论】:

  • “数组”?在 Python 中?你的意思是“列表”还是“元组”或“字典”?如果您的意思是“数组”,您使用的是哪个数组实现?麻木?

标签: python multithreading queue


【解决方案1】:

我同意@Ben James。尝试同时使用 deque 和 set。

代码如下:

class SetUniqueQueue(Queue):

    def _init(self, maxsize):
        self.queue = deque()
        self.setqueue = set()

    def _put(self, item):
        if item not in self.setqueue:
            self.setqueue.add(item)
            self.queue.append(item)

    def _get(self):
        return self.queue.popleft()

【讨论】:

    【解决方案2】:

    接下来是对 Lukáš Lalinský 后者solution 的改进。 重要的区别在于 put 被覆盖以确保 unfinished_tasks 准确且 join 按预期工作。

    from queue import Queue
    
    class UniqueQueue(Queue):
    
        def _init(self, maxsize):
            self.all_items = set()
            Queue._init(self, maxsize)
    
        def put(self, item, block=True, timeout=None):
            if item not in self.all_items:
                self.all_items.add(item)
                Queue.put(self, item, block, timeout)
    

    【讨论】:

      【解决方案3】:

      这是SetQueue的完整版

      import Queue
      
      class SetQueue(Queue.Queue):
          def _init(self, maxsize):
              Queue.Queue._init(self, maxsize)
              self.all_items = set()
      
          def _put(self, item):
              if item not in self.all_items:
                  Queue.Queue._put(self, item)
                  self.all_items.add(item)
      
          def _get(self):
              item = Queue.Queue._get(self)
              self.all_items.remove(item)
              return item
      

      【讨论】:

      • 我认为,最好也像 super(SetQueue, self)._init() 这样调用父函数,_init 函数没有 maxsize 参数 - __init__ 接收这个
      【解决方案4】:

      遗憾的是,我没有足够的评价来评论 Lukáš Lalinský 的最佳答案。

      要为 Lukáš Lalinský 的 SetQueue 的第二个变体添加对 SetQueue.task_done()SetQueue.join() 的支持,请将 else brahch 添加到 if:

      def _put(self, item):
          if item not in self.all_items:
              Queue._put(self, item);
              self.all_items.add(item);
          else:
              self.unfinished_tasks -= 1;
      

      经过测试并适用于 Python 3.4。

      【讨论】:

        【解决方案5】:

        put方法也需要被覆盖,否则join调用会永远阻塞 https://github.com/python/cpython/blob/master/Lib/queue.py#L147

        class UniqueQueue(Queue):
        
            def put(self, item, block=True, timeout=None):
                if item not in self.queue: # fix join bug
                    Queue.put(self, item, block, timeout)
        
            def _init(self, maxsize):
                self.queue = set()
        
            def _put(self, item):
                self.queue.add(item)
        
            def _get(self):
                return self.queue.pop()
        

        【讨论】:

          【解决方案6】:

          而不是“已访问的页面数组”,而是“已添加到队列中的页面数组”

          【讨论】:

            【解决方案7】:

            如果您不关心处理项目的顺序,我会尝试在内部使用 setQueue 的子类:

            class SetQueue(Queue):
            
                def _init(self, maxsize):
                    self.maxsize = maxsize
                    self.queue = set()
            
                def _put(self, item):
                    self.queue.add(item)
            
                def _get(self):
                    return self.queue.pop()
            

            正如 Paul McGuire 所指出的,这将允许在从“待处理”集中删除且尚未添加到“已处理”集中的重复项目之后添加。为了解决这个问题,您可以将这两个集合存储在Queue 实例中,但是由于您使用较大的集合来检查项目是否已被处理,您也可以返回到queue,它将正确排序请求。

            class SetQueue(Queue):
            
                def _init(self, maxsize):
                    Queue._init(self, maxsize) 
                    self.all_items = set()
            
                def _put(self, item):
                    if item not in self.all_items:
                        Queue._put(self, item) 
                        self.all_items.add(item)
            

            与单独使用一个集合相比,这样做的优点是Queue 的方法是线程安全的,因此您不需要额外的锁定来检查另一个集合。

            【讨论】:

            • 这冒着在弹出条目后重新处理条目的风险。
            • 当然,您也可以将所有项目的集合存储在“队列”中并修改_put 以首先检查该集合。它受 Queue 的锁定保护,因此没有竞争条件。
            • 这太优雅了。非常好,即使有第一个版本的缺点。
            • 即使您确实关心订单,同样的想法也有效——只需使用链接自collections 文档的OrderedSet 配方代替set
            • 注意 this answer 也涉及覆盖 put 方法。
            【解决方案8】:

            为什么只使用数组(理想情况下,字典会更好)来过滤您已经访问过的内容?将它们排队后立即将它们添加到您的数组/字典中,并且仅当它们尚未在数组/字典中时才将它们添加到队列中。然后你有 3 个简单的独立的东西:

            1. 尚未看到链接(既不在队列中也不在数组/字典中)
            2. 计划访问的链接(在队列和数组/字典中)
            3. 已访问的链接(在数组/字典中,不在队列中)

            【讨论】:

            • 保留所有先前排队的条目的列表很重要(我会使用集合,而不是列表,不确定@sam 的集合问题是什么)。如果您只是在队列中搜索重复项,您可能会重新处理之前排队并已经处理的条目,从而从队列中删除。
            • 是的,我的回答假设除了队列之外还有第二个数据结构(因此像“在队列和数组/字典中”和“在数组/字典中,不在队列中”)。在对它们进行排队之前,您将项目添加到“已见”数据结构中。您不搜索队列,而是搜索“已看到”数组。根据定义,“seen”数组中的任何内容要么在队列中,要么已经访问过;这些案件都不需要再次排队。主要技巧是确保 check-'seen'-and-queue-if-not-found 是原子的。
            【解决方案9】:

            使用:

            url in q.queue
            

            如果 url 在队列中,则返回 True

            【讨论】:

            • 如果它已经被出列和处理,这将无济于事。
            【解决方案10】:

            另外,您可以尝试使用字典而不是集合。当集合很大时,对集合的操作往往会变得相当慢,而字典查找则既好又快。

            我的 2c。

            【讨论】:

            • 这是不正确的,set 类型和dict 类型一样是一个哈希表。
            • 没错,这是错误信息。集合与 dicts 一样快(甚至可能更快,因为不需要检索/存储任何值)
            【解决方案11】:

            我解决这个问题的方法(实际上我是在 Scala 中做的,而不是 Python)是同时使用 Set 和 Queue,如果它们在集合中不存在,则只添加到队列(和集合)的链接。

            集合和队列都封装在一个线程中,只向消费者线程公开一个类似队列的接口。

            编辑:如果访问的 URL 集需要变大,其他人建议使用 SQLite,这也是我正在考虑的问题。 (目前每次爬取只有几百页,因此很容易放入内存。)但是数据库也可以封装在集合本身中,因此消费者线程无需了解它。

            【讨论】:

              【解决方案12】:

              SQLite 使用起来非常简单,非常适合……只是一个建议。

              【讨论】:

              • 如果您选择使用磁盘数据库,还可以为您提供持久性。如果遇到未处理的异常,您可以修复错误并从中断处继续
              • 这就像说Using a if condition would fit perfectly..... N 上下文的问题.. 使用 SQLite 会减慢整个过程
              猜你喜欢
              • 1970-01-01
              • 2013-05-06
              • 2011-08-18
              • 1970-01-01
              • 1970-01-01
              • 2021-02-01
              • 2014-02-21
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多