How to make worker threads quit after work is finished in a multithreaded producer-consumer pattern?

后端 未结 4 1623
时光说笑
时光说笑 2021-02-05 12:56

I am trying to implement a multithreaded producer-consumer pattern using Queue.Queue in Python 2.7. I am trying to figure out how to make the consumers, i.e. the worker threads,

相关标签:
4条回答
  • 2021-02-05 13:30

    Can the method of sending a single exit indicator for all threads (as explained in the second comment of https://stackoverflow.com/a/19369877/1175080 by Martin James) even work?

    As you have notice it can't work, spreading the message will make the last thread to update the queue with one more item and since you are waiting for a queue that will never be empty, not with the code you have.

    If the answer to the previous question is "No", is there a way to solve the problem in a way that I don't have to send a separate exit indicator for each worker thread?

    You can join the threads instead of the queue:

    def worker(n, q):
        # n - Worker ID
        # q - Queue from which to receive data
        while True:
            data = q.get()
            print 'worker', n, 'got', data
            time.sleep(1)  # Simulate noticeable data processing time
            q.task_done()
            if data == -1: # -1 is used to indicate that the worker should stop
                # Requeue the exit indicator.
                q.put(-1)
                # Commit suicide.
                print 'worker', n, 'is exiting'
                break
    
    def master():
        # master() sends data to worker() via q.
        q = Queue.Queue()
    
        # Create 3 workers.
        threads = [threading.Thread(target=worker, args=(i, q)) for i in range(3)]
        for t in threads:
            threads.start()
        # Send 10 items to work on.
        for i in range(10):
            q.put(i)
            time.sleep(0.5)
    
        # Send an exit indicator for all threads to consume.
        q.put(-1)
    
        print 'waiting for workers to finish ...'
        for t in threads:
            t.join()
        print 'done'
    
    master()
    

    As the Queue documentation explain get method will rise an execption once its empty so if you know already the data to process you can fill the queue and then spam the threads:

    import Queue
    import threading
    import time
    
    def worker(n, q):
        # n - Worker ID
        # q - Queue from which to receive data
        while True:
            try:
                data = q.get(block=False, timeout=1)
                print 'worker', n, 'got', data
                time.sleep(1)  # Simulate noticeable data processing time
                q.task_done()
            except Queue.Empty:
                break
    
    
    def master():
        # master() sends data to worker() via q.
        q = Queue.Queue()
    
        # Send 10 items to work on.
        for i in range(10):
            q.put(i)
    
        # Create 3 workers.
        for i in range(3):
            t = threading.Thread(target=worker, args=(i, q))
            t.start()
    
        print 'waiting for workers to finish ...'
        q.join()
        print 'done'
    
    master()
    

    Here you have a live example

    0 讨论(0)
  • 2021-02-05 13:31

    Don't call it a special case for a task.

    Use an Event instead, with non-blocking implementation for your workers.

    stopping = threading.Event()
    
    def worker(n, q, timeout=1):
        # run until the master thread indicates we're done
        while not stopping.is_set():
            try:
                # don't block indefinitely so we can return to the top
                # of the loop and check the stopping event
                data = q.get(True, timeout)
            # raised by q.get if we reach the timeout on an empty queue
            except queue.Empty:
                continue
            q.task_done()
    
    def master():
        ...
    
        print 'waiting for workers to finish'
        q.join()
        stopping.set()
        print 'done'
    
    0 讨论(0)
  • 2021-02-05 13:32

    Just for completeness sake: You could also enqueue a stop signal which is -(thread count). Each thread then can increment it by one and re queue it only if the stop signal is != 0.

        if data < 0: # negative numbers are used to indicate that the worker should stop
            if data < -1:
                q.put(data + 1)
            # Commit suicide.
            print 'worker', n, 'is exiting'
            break
    

    But i'd personally go with Travis Mehlinger or Daniel Sanchez answer.

    0 讨论(0)
  • 2021-02-05 13:32

    In addition to @DanielSanchez excellent answer, I propose to actually rely on a similar mechanism as a Java CountDownLatch.

    The gist being,

    • you create a latch that will open only after a certain counter went down,
    • when the latch is opened, the thread(s) waiting on it will be allowed to proceed with their execution.

    • I made an overly simple example, check here for a class like example of such a latch:

      import threading
      import Queue
      import time
      
      WORKER_COUNT = 3
      latch = threading.Condition()
      count = 3
      
      def wait():
          latch.acquire()
          while count > 0:
              latch.wait()
          latch.release()
      
      def count_down():
          global count
          latch.acquire()
          count -= 1
          if count <= 0:
              latch.notify_all()
          latch.release()
      
      def worker(n, q):
          # n - Worker ID
          # q - Queue from which to receive data
          while True:
              data = q.get()
              print 'worker', n, 'got', data
              time.sleep(1)  # Simulate noticeable data processing time
              q.task_done()
              if data == -1: # -1 is used to indicate that the worker should stop
                  # Requeue the exit indicator.
                  q.put(-1)
                  # Commit suicide.
                  count_down()
                  print 'worker', n, 'is exiting'
                  break
      
      # master() sends data to worker() via q.  
      
      def master():
          q = Queue.Queue()
      
          # Create 3 workers.
          for i in range(WORKER_COUNT):
              t = threading.Thread(target=worker, args=(i, q))
              t.start()
      
          # Send 10 items to work on.
          for i in range(10):
              q.put(i)
              time.sleep(0.5)
      
          # Send an exit indicator for all threads to consume.
          q.put(-1)
          wait()
          print 'done'
      
      master()
      
    0 讨论(0)
提交回复
热议问题