Python - 单独线程上的非空共享列表显示为空

时间:2017-11-27 03:54:47

标签: python multithreading list

我有两个类 - MessageProducer和MessageConsumer。

MessageConsumer执行以下操作:

  1. 接收消息并将其放入消息列表“_unprocessed_msgs”
  2. 在单独的工作线程上,将消息移动到内部列表“_in_process_msgs”
  3. 工作线程上的
  4. 处理来自“_in_process_msgs”
  5. 的消息

    在我的开发环境中,我遇到上面#2的问题 - 在执行步骤#1添加消息后,当工作线程检查“_unprocessed_msgs”的长度时,它将其设置为零。 重复步骤#1时,列表会在添加项目的线程上正确显示2个项目。但是在步骤#2中,在工作线程上,len(_unprocessed_msgs)再次返回零。

    不确定为什么会这样。非常感谢您对此提供帮助。

    我正在使用具有Python 2.7.12的Ubuntu 16.04。

    以下是示例源代码。如果需要更多信息,请告诉我。

    INFO: MessageConsumerThread::_queue_unprocessed_msgs: len(self._unprocessed_msg_q)=0
    INFO: Before: MessageConsumerThread::receive_msg: len(self._unprocessed_msg_q)=0
    INFO: After: MessageConsumerThread::receive_msg: **len(self._unprocessed_msg_q)=1**
    INFO: MessageConsumerThread::_queue_unprocessed_msgs: **len(self._unprocessed_msg_q)=0**
    INFO: MessageConsumerThread::_queue_unprocessed_msgs: len(self._unprocessed_msg_q)=0
    INFO: Before: MessageConsumerThread::receive_msg: len(self._unprocessed_msg_q)=1
    INFO: After: MessageConsumerThread::receive_msg: **len(self._unprocessed_msg_q)=2**
    INFO: MessageConsumerThread::_queue_unprocessed_msgs: **len(self._unprocessed_msg_q)=0**
    

    以下是我得到的日志:

    {{1}}

1 个答案:

答案 0 :(得分:0)

这对您的申请来说不是一个好的设计。 我花了一些时间来尝试调试它 - 但线程代码自然很复杂,所以我们应该尝试使其复杂化,而不是让它更加安全。

当我在Python中看到线程代码时,我通常会看到它以一种过程形式编写:一个普通函数,作为驱动每个线程的threading.Thread参数传递给target。这样,您就不需要为具有单个实例的新类编写代码。

另一件事是,虽然Python的全局解释器锁本身保证如果在两个单独的线程中修改列表不会被破坏,但列表不是推荐的“线程数据传递”数据结构。您可能应该threading.Queue来查看

这个代码中的错误乍一看可能不是由于您使用锁而导致问题的原因,但可能是。而不是

self._unprocessed_msg_q = []

将创建一个新的列表对象,另一个线程暂时没有引用(所以它可能会将数据写入旧列表),你应该这样做:

self._unprocessed_msg_q[:]  = []

或者只是你在另一种方法上做的del切片。

但是为了处于更安全的一面,并且拥有模式可维护和不太令人惊讶的代码,你应该在那里改变一种程序方法,假设是Python线程。假设“Thread”是可以做其事情的“最终”对象,然后使用Queues:

# coding: utf-8
from __future__ import print_function
from __future__ import unicode_literals

from threading import Thread
try:
    from queue import Queue, Empty
except ImportError:
    from Queue import Queue, Empty
import time
import random


TERMINATE_SENTINEL = object()
NO_DATA_SENTINEL = object()


class Receiver(object):

    def __init__(self, queue):
        self.queue = queue
        self.in_process = []

    def receive_data(self, data):
        self.in_process.append(data)

    def consume_data(self):
        print("received data:",  self.in_process)
        del self.in_process[:]

    def receiver_loop(self):
        queue = self.queue
        while True:
            try:
                data = queue.get(block=False)
            except Empty:
                print("got no data from queue")
                data = NO_DATA_SENTINEL

            if data is TERMINATE_SENTINEL:
                print("Got sentinel: exiting receiver loop")
                break

            self.receive_data(data)

            time.sleep(random.uniform(0, 0.3))
            if queue.empty():
                # Only process data if we have nothing to receive right now:
                self.consume_data()
                print("sleeping receiver")
                time.sleep(1)
        if self.in_process:
            self.consume_data()


def producer_loop(queue):
    for i in range(10):
        time.sleep(random.uniform(0.05, 0.4))
        print("putting {0} in queue".format(i))
        queue.put(i)


def main():
    msg_queue = Queue()
    msg_receiver_thread = Thread(target=Receiver(msg_queue).receiver_loop)
    time.sleep(0.1)
    msg_producer_thread = Thread(target=producer_loop, args=(msg_queue,))

    msg_receiver_thread.start()
    msg_producer_thread.start()
    msg_producer_thread.join()
    msg_queue.put(TERMINATE_SENTINEL)
    msg_receiver_thread.join()

if __name__ == '__main__':
    main()

请注意,由于您希望在recever线程中使用多个方法来处理数据,因此我使用了一个类 - 但它不会从Thread继承,也不必担心它的工作原理。它的所有方法都在同一个线程中调用:不需要锁,也不用担心接收器类本身内的竞争条件。为了在课外进行通信,Queue类的结构可以为我们处理任何竞争条件。

生产者循环,因为它只是一个虚拟生产者,根本不需要以类形式编写。但如果有更多的方法,它看起来会一样。

(随机睡眠有助于可视化“真实世界”消息接收中会发生什么) 此外,您可能想看看类似的东西: https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose