簡體   English   中英

在將Queue傳遞給子進程中的線程時,如何解決“ TypeError:無法腌制_thread.lock對象”

[英]How to fix 'TypeError: can't pickle _thread.lock objects' when passing a Queue to a thread in a child process

我整天都在這個問題上受困,而且我找不到與我要完成的任務有關的任何解決方案。

我試圖將Queues傳遞給子進程中產生的線程。 隊列是在入口文件中創建的,並作為參數傳遞給每個子流程。

我正在制作一個模塊化程序,以便a)運行神經網絡b)在需要時自動更新網絡模型c)將事件/圖像從神經網絡記錄到服務器。 我以前的程序只是偶像一個運行多個線程的CPU內核,而且運行速度相當慢,因此我決定需要對程序的某些部分進行子處理,以便它們可以在自己的內存空間中發揮最大的潛力。

子過程:

  1. 客戶端-服務器通信
  2. 網絡攝像頭控制和圖像處理
  3. 神經網絡的推理(有兩個神經網絡,每個神經網絡都有各自的過程)

共4個子流程。

在開發過程中,我需要在每個流程之間進行交流,以使它們都與來自服務器等的事件都在同一頁面上。 據我所知,Queue是最好的選擇。

(澄清:來自“多重處理”模塊的“隊列”,而不是來自“隊列”模塊的)

~~但是~~

這些子流程中的每一個都產生自己的線程。 例如,第一個子進程將產生多個線程:每個隊列一個線程,以偵聽來自不同服務器的事件並將它們傳遞到程序的不同區域; 一個線程偵聽Queue從其中一個神經網絡接收圖像; 一個線程監聽隊列,從網絡攝像頭接收實時圖像; 一個線程監聽Queue從另一個神經網絡接收的輸出。

我可以毫無問題地將隊列傳遞給子流程,並可以有效地使用它們。 但是,當我嘗試將它們傳遞給每個子進程中的線程時,出現上述錯誤。

我對多重處理還很陌生。 但是,除了共享內存空間和GIL之外,其背后的方法看起來與線程相對相同。

這是來自Main.py; 程序入口。

from lib.client import Client, Image

from multiprocessing import Queue, Process

class Main():

    def __init__(self, server):

        self.KILLQ = Queue()
        self.CAMERAQ = Queue()

        self.CLIENT = Client((server, 2005), self.KILLQ, self.CAMERAQ)
        self.CLIENT_PROCESS = Process(target=self.CLIENT.do, daemon=True)

        self.CLIENT_PROCESS.start()

if __name__ == '__main__':
    m = Main('127.0.0.1')
    while True:
        m.KILLQ.put("Hello world")

這是來自client.py(在名為lib的文件夾中)

class Client():

    def __init__(self, connection, killq, cameraq):

        self.TCP_IP = connection[0]
        self.TCP_PORT = connection[1]

        self.CAMERAQ = cameraq
        self.KILLQ = killq

        self.BUFFERSIZE = 1024
        self.HOSTNAME = socket.gethostname()

        self.ATTEMPTS = 0

        self.SHUTDOWN = False

        self.START_CONNECTION = MakeConnection((self.TCP_IP, self.TCP_PORT))

        # self.KILLQ_THREAD = Thread(target=self._listen, args=(self.KILLQ,), daemon=True)

        # self.KILLQ_THREAD.start()

    def do(self):
        # The function ran as the subprocess from Main.py
        print(self.KILLQ.get())

    def _listen(self, q):
        # This is threaded multiple times listening to each Queue (as 'q' that is passed when the thread is created)
        while True:
            print(self.q.get())

# self.KILLQ_THREAD = Thread(target=self._listen, args=(self.KILLQ,), daemon=True)

這是引發錯誤的地方。 如果我將此行保留為注釋,則程序運行正常。 我可以在此子進程(即函數“ _listen”)下的線程中沒有問題(即函數“ do”)的隊列中進行讀取。

我需要能夠跨每個流程進行交流,以便它們可以與主程序保持一致(即,在神經網絡模型更新的情況下,需要關閉推理子流程,以便可以在不引起錯誤的情況下更新模型)。

任何幫助都會很棒!

我也對其他可行的交流方法持開放態度。 如果您認為更好的溝通流程會起作用; 它需要足夠快以支持從相機發送到服務器的4k圖像的實時流傳輸。

非常感謝您的寶貴時間! :)

隊列不是問題。 multiprocessing程序包中的程序被設計為可腌制的,因此它們可以在進程之間共享。

問題是,您的線程KILLQ_THREAD是在主進程中創建的。 線程不得在進程之間共享。 實際上,當一個進程遵循POSIX標准進行分叉時,父進程中處於活動狀態的線程屬於克隆到新子級內存空間的進程映像的一部分。 原因之一是在調用fork()時互斥鎖的狀態可能導致子進程死鎖。

您必須將線程的創建移至子進程,即

def do(self):
    self.KILLQ_THREAD = Thread(target=self._listen, args=(self.KILLQ,), daemon=True)
    self.KILLQ_THREAD.start()

據推測, KILLQ應該發信號通知子進程關閉。 在這種情況下,尤其是如果您計划使用多個子進程,隊列並不是實現該目標的最佳方法。 由於Queue.get()Queue.get_nowait()從隊列中刪除了該項目,因此每個項目只能由一個使用者檢索和處理。 生產者必須將多個關閉信號放入隊列。 在多消費者方案中,您也沒有合理的方法來確保特定消費者收到任何特定商品。 從隊列中讀取的任何消費者都可以檢索放入隊列中的任何項目。

對於發信號,尤其是對於多個收件人,最好使用Event

您還會注意到,程序啟動后似乎很快掛起。 這是因為您同時啟動了子進程和帶有daemon=True的線程。

當您的Client.do()方法如上所示(即創建並啟動線程,然后退出)時,您的子進程將在對self.KILLQ_THREAD.start()的調用之后立即結束,並且守護線程將立即終止。 您的主進程什么都沒注意到,並繼續將Hello world放入隊列,直到最終填滿並queue.Full引發。

這是一個精簡的代碼示例,其中使用一個Event在兩個子進程(每個線程一個)中關閉信號。

main.py

import time    
from lib.client import Client
from multiprocessing import Process, Event

class Main:

    def __init__(self):
        self.KILLQ = Event()
        self._clients = (Client(self.KILLQ), Client(self.KILLQ))
        self._procs = [Process(target=cl.do, daemon=True) for cl in self._clients]
        [proc.start() for proc in self._procs]

if __name__ == '__main__':
    m = Main()
    # do sth. else
    time.sleep(1)
    # signal for shutdown
    m.KILLQ.set()
    # grace period for both shutdown prints to show
    time.sleep(.1)

client.py

import multiprocessing
from threading import Thread

class Client:

    def __init__(self, killq):
        self.KILLQ = killq

    def do(self):
        # non-daemonic thread! We want the process to stick around until the thread 
        # terminates on the signal set by the main process
        self.KILLQ_THREAD = Thread(target=self._listen, args=(self.KILLQ,))
        self.KILLQ_THREAD.start()

    @staticmethod
    def _listen(q):
        while not q.is_set():
            print("in thread {}".format(multiprocessing.current_process().name))
        print("{} - master signalled shutdown".format(multiprocessing.current_process().name))

產量

[...]
in thread Process-2
in thread Process-1
in thread Process-2
Process-2 - master signalled shutdown
in thread Process-1
Process-1 - master signalled shutdown

Process finished with exit code 0

至於進程間通信的方法,您可能需要研究流服務器解決方案。 Miguel Grinberg早在2014年就撰寫了有關Flask視頻流的出色教程, 並於2017年8月進行了后續跟蹤

暫無
暫無

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

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