两个线程可以同时从同一个QList中读取

Can two threads read from the same QList at the same time?

本文关键字:同一个 QList 读取 两个 线程      更新时间:2023-10-16

对线程来说很陌生,我有这个QList,线程之间共享它。它们都有自己的工作空间,GUI(模型/视图)不断地访问这个列表。然后我得到了这个指向QDataList.size()的崩溃。调试对我没有真正的帮助,因为如果我遍历代码,并且当我尝试崩溃的qList时,没有可用的信息,我从来没有遇到过这个问题。

所以,我的问题是:有可能同时获得Qlists的大小和读取对象吗?列表中的对象是线程安全的,不能由不同的线程同时读取/写入。

正在获取"0xC0000005:读取位置0xfeeefefa的访问冲突。",这指向:qlist.h 中的inline int size()常量

我浏览了调用堆栈,发现了这个:

QtCored4.dll!QListData::size()  Line 98 + 0x11 bytes    C++
QtNetworkd4.dll!QList<enum QNetworkReplyImplPrivate::InternalNotifications>::size()  Line 137 + 0x10 bytes  C++
QtNetworkd4.dll!QNetworkReplyImplPrivate::resumeNotificationHandling()  Line 444 + 0xe bytes    C++
QtNetworkd4.dll!QNetworkReplyImplPrivate::finished()  Line 797  C++
QtNetworkd4.dll!QNetworkAccessBackend::finished()  Line 313 C++
QtNetworkd4.dll!QNetworkAccessHttpBackend::replyFinished()  Line 739    C++
QtNetworkd4.dll!QNetworkAccessHttpBackend::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a)  Line 86 + 0x8 bytes    C++
QtCored4.dll!QMetaCallEvent::placeMetaCall(QObject * object)  Line 525 + 0x1d bytes C++
QtCored4.dll!QObject::event(QEvent * e)  Line 1195 + 0x14 bytes C++
QtGuid4.dll!QApplicationPrivate::notify_helper(QObject * receiver, QEvent * e)  Line 4550 + 0x11 bytes  C++
QtGuid4.dll!QApplication::notify(QObject * receiver, QEvent * e)  Line 3932 + 0x10 bytes    C++
QtCored4.dll!QCoreApplication::notifyInternal(QObject * receiver, QEvent * event)  Line 876 + 0x15 bytes    C++
QtCored4.dll!QCoreApplication::sendEvent(QObject * receiver, QEvent * event)  Line 231 + 0x39 bytes C++
QtCored4.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver, int event_type, QThreadData * data)  Line 1500 + 0xd bytes   C++
QtCored4.dll!qt_internal_proc(HWND__ * hwnd, unsigned int message, unsigned int wp, long lp)  Line 496 + 0x10 bytes C++

每个线程都有一个网络管理器来执行网络请求:

QMutexLocker中的QThread ASSERT故障:";QMutex指针未对准";,

我意识到可以预先分配一个QList,并让线程在该列表的不同区域上操作,但在我看来,这是一种糟糕的模式。

当我使用Qt时(我实际上使用PyQt,因为我是一名python程序员),我觉得最好利用提供给您的信号/插槽机制,并且永远不要在线程之间共享内存。每个线程都应该在创建时直接获得自己的数据,或者随着时间的推移通过等待的队列获得数据。当它完成工作或大块工作时,它可以发出带有数据的信号。您将有一个连接到所有线程的单个处理程序来侦听准备好的数据。

这种模式的结果是,您不需要共享内存,也不必太担心锁,而只需要等待工作人员发出数据准备就绪的信号,并且单个处理程序正在收集和更新主模型。

话虽如此,这里还有另一个关于某人使用QList作为共享内存并在锁定它之前经历崩溃的参考:http://developer.qt.nokia.com/forums/viewthread/13049

我认为,当人们(包括我自己)第一次开始使用线程时,最直接的冲动就是像往常一样使用容器。但是,一旦开始线程化,您就立即增加了代码逻辑的复杂性以及错误的容量。共享内存的同步是一种方法,在访问之前使用互斥锁来锁定资源。但我认为值得一提的是另一种沟通方式。

谷歌Go语言的核心原则之一是:"不要通过分享记忆来交流;通过交流来分享记忆"http://golang.org/doc/codewalk/sharemem/

Go希望通过通道对象通信内存来解决这个核心问题,通道对象类似于Qt中的信号/插槽。一个组件具有对内存的独占本地访问权限,然后通过通道将其传递给另一个组件。这保证了你不会有比赛条件。无论如何,我只是觉得我会加上这个额外的参考,因为我觉得它与线程编程问题非常相关。

不,他们不能错误,也许/可能。文档中说,所有QList函数都只是reentrant,而不是线程安全的。现在,通常从共享结构中读取应该是线程安全的,但在这种情况下,Qt有明确的文档,读取也没有例外。因此,您必须假设这是不允许的,并可能导致错误。

请注意,std::list不存在此问题,并且允许从多个线程进行读取(当然,写入必须仍然是独占的)。

注意,Qt线程总体上有很多特殊情况,提示我编写"线程化API的需求",其中许多Qt失败。其中一部分是历史,另一部分是处理各种硬件,但总的来说,Qt线程必须经过特殊处理。


由于文档不一致(见注释),我对QList源代码进行了快速审查。像"begin"、"size"answers"first"这样的简单函数似乎是只读的。它们不会修改数据结构。因此,如果它们导致崩溃,那是因为其他线程正在同时修改列表。