带有多处理的pyserial给我一个ctype错误

pyserial with multiprocessing gives me a ctype error

您好,我正在尝试编写一个模块,让我通过 pyserial 读取和发送数据。我必须能够与我的主脚本并行读取数据。在 Whosebug 用户的帮助下,我有一个基本的工作框架 , but when I tried adding a class I created that uses pyserial (handles finding port, speed, etc) found here 我收到以下错误:

File "<ipython-input-1-830fa23bc600>", line 1, in <module>
    runfile('C:.../pythonInterface1/Main.py', wdir='C:/Users/Daniel.000/Desktop/Daniel/Python/pythonInterface1')

  File "C:...\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 827, in runfile
    execfile(filename, namespace)

  File "C:...\Anaconda3\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 110, in execfile
    exec(compile(f.read(), filename, 'exec'), namespace)

  File "C:/Users/Daniel.000/Desktop/Daniel/Python/pythonInterface1/Main.py", line 39, in <module>
    p.start()

  File "C:...\Anaconda3\lib\multiprocessing\process.py", line 112, in start
    self._popen = self._Popen(self)

  File "C:...\Anaconda3\lib\multiprocessing\context.py", line 223, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)

  File "C:...\Anaconda3\lib\multiprocessing\context.py", line 322, in _Popen
    return Popen(process_obj)

  File "C:...\Anaconda3\lib\multiprocessing\popen_spawn_win32.py", line 89, in __init__
    reduction.dump(process_obj, to_child)

  File "C:...\Anaconda3\lib\multiprocessing\reduction.py", line 60, in dump
    ForkingPickler(file, protocol).dump(obj)

ValueError: ctypes objects containing pointers cannot be pickled

这是我用来在 SerialConnection.py

中调用 class 的代码
import multiprocessing 
from time import sleep
from operator import methodcaller

from SerialConnection import SerialConnection as SC

class Spawn:
    def __init__(self, _number, _max):
        self._number = _number
        self._max = _max
        # Don't call update here

    def request(self, x):
        print("{} was requested.".format(x))

    def update(self):
        while True:
            print("Spawned {} of {}".format(self._number, self._max))
            sleep(2)

if __name__ == '__main__':
    '''
    spawn = Spawn(1, 1)  # Create the object as normal
    p = multiprocessing.Process(target=methodcaller("update"), args=(spawn,)) # Run the loop in the process
    p.start()
    while True:
        sleep(1.5)
        spawn.request(2)  # Now you can reference the "spawn"
    '''
    device = SC()
    print(device.Port)
    print(device.Baud)
    print(device.ID)
    print(device.Error)
    print(device.EMsg)
    p = multiprocessing.Process(target=methodcaller("ReadData"), args=(device,)) # Run the loop in the process
    p.start()
    while True:
        sleep(1.5)
        device.SendData('0003')

这个 class 给我带来问题我做错了什么? pyserial 和 multiprocessing 一起使用是否有某种形式的限制?我知道可以做到,但我不明白如何...

这是我从 python

得到的回溯
Traceback (most recent call last):   File "C:...\Python\pythonInterface1\Main.py", line 45, in <module>
    p.start()

  File "C:...\AppData\Local\Programs\Python\Python36-32\lib\multiprocessing\process.py", line 105, in start
    self._popen = self._Popen(self)

  File "C:...\AppData\Local\Programs\Python\Python36-32\lib\multiprocessing\context.py", line 223, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)

  File "C:...\AppData\Local\Programs\Python\Python36-32\lib\multiprocessing\context.py", line 322, in _Popen
    return Popen(process_obj)

  File "C:...\AppData\Local\Programs\Python\Python36-32\lib\multiprocessing\popen_spawn_win32.py", line 65, in __init__
    reduction.dump(process_obj, to_child)

  File "C:...\AppData\Local\Programs\Python\Python36-32\lib\multiprocessing\reduction.py", line 60, in dump
    ForkingPickler(file, protocol).dump(obj) ValueError: ctypes objects containing pointers cannot be pickled

我认为问题是由于 device 内部的某些东西是 unpicklable(即不能被 python 序列化)。查看 this page 看看您是否可以看到任何可能被设备对象中的某些内容破坏的规则。

那么为什么 device 需要 picklable?

启动 multiprocessing.Process 时,它会在操作系统级别使用 fork()(除非另有说明)来创建新进程。这意味着父进程的整个上下文 "copied" 都交给了子进程。 不需要酸洗,因为它是在操作系统级别完成的。

(注意:至少在 unix 上,这个 "copy" 实际上是一个相当便宜的操作,因为它使用了一个叫做 "copy-on-write" 的特性。这意味着父进程和子进程实际上从同一个内存读取直到一个或另一个修改它,此时原始状态被复制到子进程。)

但是,您希望进程处理 do 的函数参数必须被 pickle,因为它们不是主进程上下文的一部分。因此,这包括您的 device 变量。

我认为您可以通过允许将 device 作为分叉操作的一部分进行复制而不是将其作为变量传递来解决您的问题。不过,要做到这一点,您需要一个包装函数来围绕您希望进程执行的操作,在本例中为 methodcaller("ReadData")。像这样:

if __name__ == "__main__":

    device = SC()

    def call_read_data():
        device.ReadData()

    ...

    p = multiprocessing.Process(target=call_read_data) # Run the loop in the process
    p.start()

您正在尝试将 SerialConnection 实例作为参数传递给另一个进程。为此 python 必须首先序列化(pickle)对象,而 SerialConnection 对象是不可能的。

中所述,一个可能的解决方案是允许使用调用 multiprocessing.Process.start 时发生的 fork 将 SerialConnection 对象复制到其他进程的内存中,但这不适用于 Windows,因为它不使用 fork。

在代码中实现并行性的一种更简单、跨平台且更有效的方法是使用线程而不是进程。对代码的更改很少:

import threading
p = threading.Thread(target=methodcaller("ReadData"), args=(device,))