在子进程中将队列传递给线程时,如何解决“ TypeError:无法腌制_thread.lock对象”

时间:2019-05-22 01:54:48

标签: python multithreading multiprocessing queue

我整天都在解决这个问题,而我找不到能够解决我要解决的问题的解决方案。

我试图将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图像的实时流传输。

非常感谢您的宝贵时间! :)

1 个答案:

答案 0 :(得分:1)

队列不是问题。 multiprocessing软件包中的软件包被设计为可腌制的,以便它们可以在进程之间共享。

问题是,您的线程KILLQ_THREAD是在主进程中创建的。线程不得在进程之间共享。实际上,当一个进程遵循POSIX标准进行分叉时,父进程中处于活动状态的线程不是克隆到新子级内存空间的进程映像的 not 部分。原因之一是调用fork()时互斥锁的状态可能导致子进程死锁。

您必须将线程的创建移至子进程,即

def do(self):
    self.KILLQ_THREAD = Thread(target=self._listen, args=(self.KILLQ,), daemon=True)
    self.KILLQ_THREAD.start()
print(self.q.get())中的

Client._listen()应该是print(q.get())。您似乎在q上没有实例属性Client

假定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! 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年在Video Streaming with Flask上撰写了一篇精彩的教程,内容是follow-up from August 2017