求助: PyQt5 的一个线程占用 CPU 导致另一个线程响应变慢

2020-05-10 22:24:49 +08:00
 wangyz1997

我之前是搞嵌入式的,现在要写一个上位机,选择使用 Python+PyQt5 来完成。我的程序有两个执行线程以及一个主线程,主线程初始化完两个执行线程之后,一个执行线程进行串口的数据交互,另一个线程执行一个比较耗时的操作( OpenCV 图像处理),完全占满 CPU 。 这两个线程都是使用 QThread 来完成的,现在遇到了一个问题:图像处理线程长时间的计算,会导致串口线程出现响应变慢的现象(串口丢包导致通信失败),与此同时主线程的 UI 操作也会卡顿。 下面是部分代码。

class MainWindowClass(QMainWindow, Ui_MainWindow):
	def __init__(self):
		super(MainWindowClass, self).__init__()  # 初始化父类
		self.setupUi(self)  # 初始化窗口

		self.imgProcTrd = ThreadImageProc(op_param, op_cam_idx)  # 创建图像处理线程
		self.imgProcTrd.signalImageSend.connect(self.callback_image_display)  # 连接回传到 GUI 的事件

		self.serialCommTrd = ThreadSerialComm()  # 创建串口通信线程
		self.serialCommTrd.signalSerialStatus.connect(self.callback_serial_event)


class ThreadImageProc(QThread):
	#  通过类成员对象定义信号对象
	signalImageSend = pyqtSignal(numpy.ndarray)

	def __init__(self, op_param, cam_idx):
		super(ThreadImageProc, self).__init__()

	def run(self):
		while True:
			ret, cap_img = cap.read()
			img = self.image_proc(cap_img)
			self.signalImageSend.emit(img)


class ThreadSerialComm(QThread):
	signalSerialStatus = pyqtSignal(list)  # 串口上报 

	def __init__(self):
		super(ThreadSerialComm, self).__init__()

	def run(self): 
		while True:
		# 省略串口通信函数

想请问一下各位大佬这是什么情况?有没有解决方案? RTOS 是抢占式系统,即使是单核心的嵌入式处理器也会保证每过一个 Tick 执行一次任务调度来确保高优先级的任务得以抢占 CPU 。按照我的理解,电脑这种多核心处理器应该是把多个线程分配给多个核心执行的(仅仅是我很初级的理解,望赐教),为什么会出现这种子线程卡死其他线程甚至 UI 线程的呢?有没有什么解决方法?

4071 次点击
所在节点    Python
29 条回复
jones2000
2020-05-11 14:36:02 +08:00
@wangyz1997 关键点在你的读取的这个地方, 如果你的数据是有更新频率的,那读取一次完成以后, 需要释放 cpu (使用信号等待几微妙), 在下次频率更新以后在读取。
你现在的逻辑就是一个死循环一直读, 这样就肯定是不会释放 cpu, 如果你是这样模式的,那就使用独占一个 cpu 的方式, 比如你是 16 核的服务器,你就单独拿出 1-2 个核单独计算你的图形数据。 或者单独拿一个服务器计算图形,  UI 显示用其他的机器显示, 通过 tcp 推送的方式,直接把计算好的数据发到前端, 这样前端是绘图肯定不卡。
wangyz1997
2020-05-11 19:10:48 +08:00
@jones2000 是的,我准备学习一下多进程。因为图像处理本来就很慢,希望它能够以最高的速度运行,所以是死循环读。
wangyz1997
2020-05-11 19:12:09 +08:00
@imn1 是每一帧都要捕捉。因为图像算法本来就比较慢(个位数 fps ),因此希望能够充分发挥计算能力。因为串口通信有一个超时问题,所以这两个只能分开来做了。感谢你的回复。
nightwitch
2020-05-11 23:02:15 +08:00
Python 由于 GIL 的存在,多线程是假的,实质上只有一个线程在运行,只是解释器在切换任务。opencv 的部分新起一个进程,数据传过去,计算完了以后拿回来。
youngce
2020-05-11 23:29:17 +08:00
@nightwitch #24 GIL 保证解释器同时只解释一个 py 线程的代码, 但是如果这个线程 IO 阻塞了, 解释器就跳到其它线程。 多线程是真的, 只不过由于 GIL 的存在,导致了 Cpython 中无法利用多线程实现多核处理器并发处理 CPU 密集操作。所以说到底,python 的 io 阻塞可以利用协程、多线程实现异步,而 cpu 密集的操作只能依赖于多进程。
defphilip
2020-05-12 00:18:21 +08:00
把核心的计算程序,串口读写全部封装到 C++代码上,包括开线程,然后通过事件通知的方式回调告诉主线程,这样你就可以在同一个进程空间内干这两件事情,并且你还能享受到 python 的部分遍历

另外既然都用 Qt 了,为何不选择直接用 C++ Qt 完成呢?
weyou
2020-05-12 00:28:10 +08:00
@nightwitch 你没有 get 到任何一点啊,Python 的多线程是真的,而且这里是 Qt 的多线程,也是真的。线程切换自然也是操作系统管理的,而不是 Python 解释器。只是 CPU 密集型操作下因为 GIL 的存在,导致每个线程都需要获取到 GIL 才能运行,基本“等价于”线性执行的,但不是你理解的只有一个线程存在。
enrio
2020-05-12 09:16:28 +08:00
CPU 密集型+Python+多线程=>多进程
wangyz1997
2020-05-12 09:59:20 +08:00
@defphilip 有一些库只有 Python 有,若是重做轮子太费时费力了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/670348

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX