生产者-消费者为什么不停止?

时间:2018-09-11 08:24:18

标签: python python-3.x multithreading queue producer-consumer

我找到了一个代表生产者-消费者带有两个线程的示例。但是,当我向流程发送停止信号时,它没有。它期望第二个信号例如SIGKILL完全停止。我以为问题出在task_done()上,但似乎没有。

import time

import queue
import threading
import random


class Producer(threading.Thread):
    """
    Produces random integers to a list
    """

    def __init__(self, queue):
        """
        Constructor.

        @param queue queue synchronization object
        """
        threading.Thread.__init__(self)
        self.queue = queue

    def run(self):
        """
        Thread run method. Append random integers to the integers
        list at random time.
        """
        while True:
            integer = random.randint(0, 256)
            self.queue.put(integer)
            print('%d put to queue by %s' % (integer, self.name))
            time.sleep(1)


class Consumer(threading.Thread):
    """
    Consumes random integers from a list
    """

    def __init__(self, queue):
        """
        Constructor.

        @param integers list of integers
        @param queue queue synchronization object
        """
        threading.Thread.__init__(self)
        self.queue = queue

    def run(self):
        """
        Thread run method. Consumes integers from list
        """
        while True:
            integer = self.queue.get()
            print('%d popped from list by %s' % (integer, self.name))
            self.queue.task_done()


def main():
    q = queue.Queue()
    t1 = Producer(q)
    t2 = Consumer(q)
    t1.start()
    t2.start()
    t1.join()
    t2.join()


if __name__ == '__main__':
    main()

输出:

210 put to queue by Thread-1
210 popped from list by Thread-2
Traceback (most recent call last):
  File "/Users/abc/PycharmProjects/untitled1/ssid.py", line 74, in <module>
    main()
  File "/Users/abc/PycharmProjects/untitled1/ssid.py", line 69, in main
    t1.join()
  File "/usr/local/Cellar/python3/3.6.3/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 1056, in join
    self._wait_for_tstate_lock()
  File "/usr/local/Cellar/python3/3.6.3/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 1072, in _wait_for_tstate_lock
    elif lock.acquire(block, timeout):
KeyboardInterrupt
244 put to queue by Thread-1
244 popped from list by Thread-2
85 put to queue by Thread-1
85 popped from list by Thread-2
160 put to queue by Thread-1
160 popped from list by Thread-2

1 个答案:

答案 0 :(得分:0)

这是因为KeyboardInterrupt仅停止了主线程的获取。您可以通过让子线程打印threading.enumerate()来观看此情况,该返回返回所有活动线程+主线程。

import time
import queue
import threading
import random


class Producer(threading.Thread):

    def __init__(self, queue):
        super().__init__()
        self.queue = queue

    def run(self):
        while True:
            integer = random.randint(0, 256)
            self.queue.put(integer)
            print(f'{integer} put to queue by {self.name} '
                  f'threads: {threading.enumerate()}')
            time.sleep(1)


class Consumer(threading.Thread):

    def __init__(self, queue):
        super().__init__()
        self.queue = queue

    def run(self):
        while True:
            integer = self.queue.get()
            print(f'{integer} popped from list by {self.name} '
                  f'threads:{threading.enumerate()}')
            self.queue.task_done()


def main():
    q = queue.Queue()
    t1 = Producer(q)
    t2 = Consumer(q)
    # t1.daemon = True
    # t2.daemon = True
    t1.start()
    t2.start()
    t1.join()
    t2.join()


if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print('got KeyboardInterrupt')

带有KeyboardInterrupt的示例输出。请注意在KeyboardInterrupt之后列为“已停止”的MainThread:

97 put to queue by Thread-1 threads: [<_MainThread(MainThread, started 
139810293606208)>, <Producer(Thread-1, started 139810250913536)>, 
<Consumer(Thread-2, started 139810242520832)>]
97 popped from list by Thread-2 threads:[<_MainThread(MainThread, started 
139810293606208)>, <Producer(Thread-1, started 139810250913536)>, 
<Consumer(Thread-2, started 139810242520832)>]
got KeyboardInterrupt
92 put to queue by Thread-1 threads: [<_MainThread(MainThread, stopped 
139810293606208)>, <Producer(Thread-1, started 139810250913536)>, 
<Consumer(Thread-2, started 139810242520832)>]
92 popped from list by Thread-2 threads:[<_MainThread(MainThread, stopped 
139810293606208)>, <Producer(Thread-1, started 139810250913536)>, 
<Consumer(Thread-2, started 139810242520832)>]

您可以创建子线程守护程序,以使其与主线程一起退出。但这仅应在线程没有任何资源的情况下考虑:

  

注意守护程序线程在关闭时突然停止。它们的资源(例如打开的文件,数据库事务等)可能无法正确释放。如果您希望线程正常停止,请使其成为非守护线程,并使用适当的信令机制,例如事件docs

更好的方法是像上面的代码那样捕获KeyboardInterrupt并在队列中向子线程发送一个哨兵值,以使它们知道它们应该完成,从而允许它们在执行清理之前退出。