簡體   English   中英

Python-單獨線程上的非空共享列表顯示為空

[英]Python - Non-empty shared list on separate thread appears empty

我有兩個類-MessageProducer和MessageConsumer。

MessageConsumer執行以下操作:

  1. 接收消息並將其放入消息列表“ _unprocessed_msgs”
  2. 在單獨的工作線程上,將消息移至內部列表“ _in_process_msgs”
  3. 在工作線程上,處理來自“ _in_process_msgs”的消息

在我的開發環境中,我面臨上述#2的問題-通過執行步驟#1添加一條消息后,當工作線程檢查“ _unprocessed_msgs”的長度時,它將變為零。 當重復步驟1時,列表會在添加了該項目的線程上正確顯示2個項目。 但是在步驟2中,在工作線程上,len(_unprocessed_msgs)再次返回零。

不知道為什么會這樣。 非常感謝幫助。

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

下面是示例源代碼。 如果需要更多信息,請告訴我。

import threading
import time
class MessageConsumerThread(threading.Thread):
    def __init__(self):
        super(MessageConsumerThread, self).__init__()
        self._unprocessed_msg_q = []
        self._in_process_msg_q = []
        self._lock = threading.Lock()
        self._stop_processing = False

    def start_msg_processing_thread(self):
        self._stop_processing = False
        self.start()

    def stop_msg_processing_thread(self):
        self._stop_processing = True

    def receive_msg(self, msg):
        with self._lock:
            LOG.info("Before: MessageConsumerThread::receive_msg: "
                     "len(self._unprocessed_msg_q)=%s" %
                     len(self._unprocessed_msg_q))
            self._unprocessed_msg_q.append(msg)
            LOG.info("After: MessageConsumerThread::receive_msg: "
                     "len(self._unprocessed_msg_q)=%s" %
                     len(self._unprocessed_msg_q))

    def _queue_unprocessed_msgs(self):
        with self._lock:
            LOG.info("MessageConsumerThread::_queue_unprocessed_msgs: "
                     "len(self._unprocessed_msg_q)=%s" %
                     len(self._unprocessed_msg_q))
            if self._unprocessed_msg_q:
                LOG.info("Moving messages from unprocessed to in_process queue")
                self._in_process_msg_q += self._unprocessed_msg_q
                self._unprocessed_msg_q = []
                LOG.info("Moved messages from unprocessed to in_process queue")

    def run(self):
        while not self._stop_processing:
            # Allow other threads to add messages to message queue
            time.sleep(1)

            # Move unprocessed listeners to in-process listener queue
            self._queue_unprocessed_msgs()

            # If nothing to process continue the loop
            if not self._in_process_msg_q:
                continue

            for msg in self._in_process_msg_q:
                self.consume_message(msg)

            # Clean up processed messages
            del self._in_process_msg_q[:]

    def consume_message(self, msg):
        print(msg)


class MessageProducerThread(threading.Thread):
    def __init__(self, producer_id, msg_receiver):
        super(MessageProducerThread, self).__init__()
        self._producer_id = producer_id
        self._msg_receiver = msg_receiver

    def start_producing_msgs(self):
        self.start()

    def run(self):
        for i in range(1,10):
            msg = "From: %s; Message:%s" %(self._producer_id, i)
            self._msg_receiver.receive_msg(msg)


def main():
    msg_receiver_thread = MessageConsumerThread()
    msg_receiver_thread.start_msg_processing_thread()

    msg_producer_thread = MessageProducerThread(producer_id='Producer-01',
                                                msg_receiver=msg_receiver_thread)
    msg_producer_thread.start_producing_msgs()
    msg_producer_thread.join()
    msg_receiver_thread.stop_msg_processing_thread()
    msg_receiver_thread.join()

if __name__ == '__main__':
    main()

以下是我得到的日志:

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**

對於您的應用程序而言,這不是一個好的設計。 我花了一些時間來調試它-但是線程代碼自然很復雜,因此我們應該盡量簡化它,而不是讓它更加混亂。

當我看到Python中的線程代碼時,通常會看到它以程序形式編寫:一個普通函數,該函數作為驅動每個線程的target參數傳遞給threading.Thread 這樣,您無需為將具有單個實例的新類編寫代碼。

另一件事是,盡管Python的全局解釋器鎖本身保證了如果在兩個單獨的線程中進行修改,列表也不會損壞,但是列表並不是推薦的“線程數據傳遞”數據結構。 您可能應該看一下threading.Queue來做到這一點

乍一看,這段代碼中的錯誤可能不是由於使用鎖而導致問題的原因,而可能是原因。 代替

self._unprocessed_msg_q = []

這將創建一個新的列表對象,另一個線程暫時也沒有引用(因此可能會將數據寫入舊列表),您應該執行以下操作:

self._unprocessed_msg_q[:]  = []

或只是del片件事你做其他的方法。

但是為了安全起見,並擁有模式可維護且不太令人驚訝的代碼,您確實應該在此假設Python線程更改為過程方法。 假設“線程”是可以完成其任務的“最終”對象,然后在周圍使用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()

請注意,由於您希望接收線程中的多個方法可以處理數據,因此我使用了一個類-但它不繼承自Thread,因此不必擔心其工作原理。 它的所有方法都在同一個線程中調用:不需要鎖,也不必擔心接收器類內部的競爭條件。 為了在類外進行交流,Queue類的結構可以為我們處理任何競爭條件。

生產者循環,因為它只是一個虛擬生產者,根本不需要以類形式編寫。 但是,如果它有更多的方法,它將看起來一樣。

(隨機睡眠有助於可視化“現實世界”消息接收中將發生的情況)此外,您可能希望查看以下內容: https : //www.thoughtworks.com/insights/blog/composition-vs-inheritance-如何選擇

終於我解決了這個問題。 在實際的代碼中,我有一個Manager類,負責將MessageConsumerThread實例化為初始化程序中的最后一件事:

class Manager(object):
    def __init__(self):
        ...
        ...
        self._consumer = MessageConsumerThread(self)
        self._consumer.start_msg_processing_thread()

問題似乎是當Manager仍在執行初始化程序時,在MessageConsumerThread初始化程序中傳遞了“自我”(盡管這是最后兩個步驟)。 當我將消費者的創建移出初始化程序的那一刻,消費者線程能夠看到“ _unprocessed_msg_q”中的元素。

請注意,上述示例代碼仍然無法重現該問題。 它僅在生產環境中體現。 沒有上述修復程序,我也嘗試了隊列和字典,但觀察到相同的問題。 修復后,嘗試使用隊列和列表,並能夠成功執行代碼。

我非常感謝@jsbueno和@ivan_pozdeev的時間和幫助! 社區@stackoverflow非常有幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM