gpt4 book ai didi

python - 如何在运行多线程计算然后绘制发出的结果时防止崩溃

转载 作者:太空宇宙 更新时间:2023-11-03 21:16:10 24 4
gpt4 key购买 nike

我正在编写一个 PySide2 应用程序,它将结果绘制到某个计算中,并尝试对计算进行多线程处理以避免锁定 GUI。我试图使用 QThreadPool 在与绘图相关的选项交互后,在一个单独的线程中运行计算,该线程通过信号将结果返回到回调方法,该方法使用 matplotlib 绘制结果。

问题是,当我连续太快(但不是太快)更改选项选择时,应用程序崩溃。如果移除线程,则不会发生这种情况。

我知道很多问题是由于绘图发生在工作线程而不是主线程中引起的,所以我相信我已经确保绘图仅发生在主线程中。

我想部分问题是我可能会误解使用信号和槽时运行的内容。我尝试查找代码中不同点使用的线程,但只能使用 QThread.currentThread(),它返回地址,但并没有真正帮助,因为 QThread.currentThreadId() 会导致此错误: AttributeError: type object “PySide2.QtCore.QThread”没有属性“currentThreadId”。

我尝试通过编写类似崩溃的应用程序的最小版本来隔离行为,其中大部分内容我已放在下面。我已经排除了计算,因为我不确定是否可以共享它,并且我已经用带有一些选项的 QListWidget 替换了绘图选项。与适当的应用程序相比,它需要更多的交互才能崩溃,在某些情况下,在一两秒内选择几个选项后就会崩溃,但希望能说明这一点。

class MainWindow(QObject):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.main_window = QMainWindow()
self.main_window.setCentralWidget(QWidget())
self.main_window.centralWidget().setLayout(QHBoxLayout())
self.setup_main_window()

def setup_main_window(self):
print(f'setup_main_window thread address: {QThread.currentThreadId()}')
self.load_list()
self.plot_figure = PlotFigure()
self.canvas = FigureCanvas(self.plot_figure)
self.plot_figure.plot(update=False)
self.main_window.centralWidget().layout().addWidget(self.canvas)

def load_list(self):
self.order_list = QListWidget(self.main_window)
self.list_items = [
QListWidgetItem('1', self.order_list),
QListWidgetItem('2', self.order_list),
QListWidgetItem('3', self.order_list),
QListWidgetItem('4', self.order_list),
]
self.order_list.itemClicked.connect(self.order_list_item_changed)
self.main_window.centralWidget().layout().addWidget(self.order_list)

def order_list_item_changed(self):
print(f'order_list_item_changed thread address: {QThread.currentThreadId()}')
self.plot_figure.plot()

def show(self):
if self.main_window is not None:
self.main_window.show()

class PlotFigure(Figure):
def __init__(self):
super().__init__()

def plot(self):
print(f'plot thread address: {QThread.currentThreadId()}')
print(f'update: {update}')
print(f'connecting signals')
worker = Worker(self.calc)
#worker.signals.close.connect(self.set_end_calc)
worker.signals.finished.connect(self.plot_result)
print(f'threads: {QThreadPool.globalInstance().activeThreadCount()}')
QThreadPool.globalInstance().start(worker)

def plot_result(self, m, xs, ys):
print(f'plot_result thread address: {QThread.currentThreadId()}')
print('plotting')
fig = self.canvas.figure
fig.clear()
self.axis = fig.add_subplot(111)
self.image = self.axis.imshow(m,
origin='lower',
aspect='auto',
cmap=matplotlib.cm.get_cmap('inferno'),
interpolation='bilinear',
extent=(xs[0], xs[-1], ys[0], ys[-1])
)
self.canvas.draw()
class WorkerSignals(QtCore.QObject):
close = QtCore.Signal(bool)
start = QtCore.Signal(bool)
finished = QtCore.Signal(list, list, list)

class Worker(QtCore.QRunnable):
def __init__(self, worker_method):
super(Worker, self).__init__()
self.signals = WorkerSignals()
self.worker_method = worker_method

def run(self):
self.signals.close.emit(True)
print('close signal sent')
m, xs, ys = self.worker_method()
print('calc done')
self.signals.finished.emit(m, xs, ys)

我应该能够选择新选项(在列表小部件周围单击),从线程池启动一个新线程,该线程运行计算并发送回要绘制的结果。当短时间内选择太多选项时,应用程序会崩溃。当所有事情都发生在主线程中时,这种情况就不会发生。

谁能告诉我应用程序可能崩溃的原因并提供修复崩溃的解决方案吗?

最佳答案

您所描述的崩溃行为表明多个线程试图同时操作相同的数据/变量。由于您没有限制可以启动的额外线程的数量,因此如果用户快速连续触发多个工作线程,则很可能会发生这种情况。

有几种方法可以解决这个问题,您可以根据自己的要求选择哪种方法。

复制数据

如果由于两个线程访问同一内存而发生崩溃,一个简单的解决方案是获取数据的副本。请注意,对于列表、字典等,您需要深度复制数据以确保嵌套值也被复制。对于大数据,这可能会导致一些开销。

您尚未在示例中包含 calc 方法,因此我无法建议如何在您的情况下执行此操作,但一般而言。

from copy import copy
data = 'a simple string'
data_c = copy(data)

或者,深度复制

from copy import deepcopy
data = {'a':'dict', 'of':'items'}
data_c = deepcopy(data)

worker 锁

如果您有一个特定的工作线程,一次只能运行一个,则可以实现原始锁。下面假设您希望最新工作程序始终运行,并在新任务到达时丢弃所有较早的工作程序。

self._worker_lock = False
self._worker_waiting = False

每个工作人员 .finished 信号应连接到执行的方法。

def worker_finised(self):
self._worker_lock = False
if self._worker_waiting:
QThreadPool.globalInstance().start(self._worker_waiting)
self._worker_waiting = False

当前工作线程完成后,这将自动启动下一个工作线程。如果您希望所有发生的工作线程都运行,则可以使用 list 队列。

最后,启动工作线程时。

def plot()
worker = Worker(self.calc)
worker.signals.finished.connect(self.plot_result)

if self._worker_lock:
self._worker_waiting = worker
else:
self._worker_lock
QThreadPool.globalInstance().start(worker)

将线程限制为 1

这是一种愚蠢的方法,它还会阻止您使用线程进行其他计算并使线程池变得无用。

threadpool = QThreadPool()
threadpool. setMaxThreadCount(1)

即使这样,如果绘图和计算方法仍然可以同时执行,这可能还不够,您仍然会崩溃。但如果您的线程仅相互冲突,那就足够了。

关于python - 如何在运行多线程计算然后绘制发出的结果时防止崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54693147/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com