QSerialPort - 是否可以在单独的线程上读取()和write()?

时间:2014-04-01 00:22:36

标签: multithreading qt serial-port

我们有一个DLL,它为我们制作的USB设备提供API,可以显示为USB CDC com端口。我们实际上在Windows上使用自定义驱动程序以获得最佳性能以及异步i / o,但我们过去也使用了串口异步文件i / o,并且也取得了相当的成功。

在与我们的设备通信时,延迟在此API中非常重要,因此我们构建了库,以便当应用程序进行API调用以在设备上执行命令时,这些命令将直接转换为API调用者线程上的写入没有等待上下文切换。该库还维护一个监听线程,该线程始终在异步读取时使用等待对象等待新响应。这些响应被解析并插入到线程安全的队列中,供API用户在方便时阅读。

所以基本上,我们在API调用者的线程中完成了大部分的写作,并且我们在一个监听线程中读取了所有内容。我已经尝试将我们的代码版本移植到使用QSerialPort而不是Windows和OSX的本机串行文件i / o,但每当我尝试从调用者的线程写入()时,我都会遇到错误(QSerialPort是在听力线程):

QObject: Cannot create children for a parent that is in a different thread.

这似乎是由于为QSerialPortPrivate :: startAsyncWrite()使用的通知程序池创建了另一个基于QObject的WriteOverlappedCompletionNotifier。

QSerialPort的当前5.2版本是否仅限于在同一线程上进行读写操作?这似乎非常不幸,因为底层操作系统对串行端口文件i / o没有任何此类线程限制。据我所知,问题主要与所有QSerialPort的通知程序类都基于QObject的事实有关。

有没有人对此有好的工作?我可能会尝试构建我自己的QSerialPort,它使用不基于QObject的通知程序来查看这有多远。 QObject似乎在这里给出的唯一真正优势是在端口关闭时破坏通知程序。

3 个答案:

答案 0 :(得分:3)

最小影响解决方案

您可以自由检查QSerialPortQIODevice代码,看看需要更改哪些内容才能使write方法可以从一个线程进行线程安全访问。通知程序根本不需要是QSerialPort的子项,它们可以添加到在销毁时清理的指针列表中。

我的猜测是,主线代码可能不需要进行其他更改,只需要使用互斥保护来访问错误状态,但您需要确认。这对您的代码影响最小。

如果您关心发布完整性,那么您应该自己编译Qt,并且您应该将它作为您自己的源代码存储库的一部分。所以这一切都不应该是任何问题。

关于性能

“这些命令直接转换为API调用者线程上的写入,因此无需等待上下文切换”现代机器是多核的,多个线程当然可以并行运行而无需任何上下文切换。但根本问题是:为什么要这么麻烦?如果您需要硬实时保证,则需要一个硬实时系统。否则,系统中的任何内容都不应该关心这种微不足道的延迟。如果您这样做只是为了让GUI感觉响应,那么这样的过度复杂就没有意义了。

通信线程方法

我所做的,取得了巨大的成功和出色的性能,是将通信协议和通信端口放在同一个专用线程中,以及GUI线程或其他线程中的用户。通信端口通常为QIODevice,如QTcpSocketQSerialPortQLocalSocket等。由于通信协议对象“只是”QObject,因此也可以使用端口在GUI线程中实现演示目的 - 无论如何它都是完全异步设计的,除了最简单的计算外,它不会阻塞任何东西。

通信协议将多个执行请求排队。即使在单核机器上,一旦GUI线程完成提交所有请求,进一步的执行都在通信线程中。

QSerialPort实现使用异步OS API。在单独的线程上进一步处理这些异步回复几乎没有任何好处。这些操作的开销非常低,并且通过尝试这样做,您将无法获得可测量的延迟。请记住:这不是您的代码,而只是在缓冲区之间推送字节的代码。是的,在负载很重或单核系统上可能存在上下文切换开销,但除非你可以测量它的存在和不存在之间的差异,否则你就会遇到想象中的问题。

当然,只要您通过事件队列互斥锁序列化对它的访问,就可以从多个线程中使用任何 QObject。无论何时使用QMetaObject::invokeMethod或信号槽连接,都可以使用此功能。

因此,在QSerialPort周围添加一个简单的包装器,将write公开为线程安全方法。在内部,它应该使用信号槽连接。您可以从任何线程调用此线程安全的write。这种调用的开销是互斥锁和2+n malloc / free调用,其中n是非零数量的参数。

在您的包装器中,您还可以处理readyRead信号,并发出包含接收数据的信号。该信号可以由居住在另一个线程中的QObject处理。

总的来说,如果你正确地进行了测量,并且你的端口线程的实现是正确的,那么对于所有这些并发症你都应该没有任何好处。

如果您的通信协议进行大量数据处理,则应将其考虑在内。它可以进入一个单独的QObject,然后可以在自己的线程上运行。或者,可以使用由QtConcurrent::run执行的专用仿函数来完成。

答案 1 :(得分:0)

如果使用QSerialPort打开并配置串行端口,QSocketNotifier监视读取活动(以及其他QSocketNotifier实例的写入完成和错误处理,如果需要),该怎么办? ?

QSerialPort::handle应该为您提供所需的文件描述符。在Windows上,如果该函数返回Windows HANDLE,则可以使用_open_osfhandle来获取文件描述符。

答案 2 :(得分:0)

作为后续工作,在讨论后不久,我使用select()等为POSIX系统实现了自己的线程安全串口代码,并且它在Qt和非Qt应用程序的多个线程上运行良好一样。基本上,我完全放弃了使用QtSerialPort。