C++ QabstracteModel线程安全
我的C++ QabstracteModel线程安全,c++,multithreading,qt,model-view-controller,model,C++,Multithreading,Qt,Model View Controller,Model,我的qabstractemmodel实现正在侦听一些事件,并在单独的线程中处理更新。 处理更新可能导致模型中的布局和/或数据更改。 数据本身的存储是受保护的,boost::mutex,对qabstractemmodel的接口函数的每次调用(如果我理解正确,在GUI线程中执行)和更新处理函数(在单独的线程中)都会锁定mutex。 在锁定data()/rowCount()/任何试图同时获取的对象的同一互斥体时,是否可以发出layoutChanged/dataChanged信号 一段代码: class
qabstractemmodel
实现正在侦听一些事件,并在单独的线程中处理更新。
处理更新可能导致模型中的布局和/或数据更改。
数据本身的存储是受保护的,boost::mutex
,对qabstractemmodel
的接口函数的每次调用(如果我理解正确,在GUI线程中执行)和更新处理函数(在单独的线程中)都会锁定mutex。
在锁定data()/rowCount()/任何试图同时获取的对象的同一互斥体时,是否可以发出layoutChanged/dataChanged信号
一段代码:
class MyItemModel : public QAbstractItemModel {
Q_OBJECT
public:
void processUpdate(const Update& update) {
Mservice.post([this, update]() {
boost::lock_guard<boost::mutex> lock (Mlock);
bool willModifyLayout = checkWillModifyLayout(update)
bool willModifyData = checkWillModifyData(update);
if (willModifyLayout) {
emit layoutAboutToBeChanged();
}
Mdata.processUpdate(update);
if (willModifyLayout) {
emit layoutChanged();
}
else if (willModifyData) {
emit dataChanged();
}
});
}
virtual QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE {
boost::lock_guard<boost::mutex> lock (Mlock);
if (index.isValid()) return Mdata.data(index, role);
}
private:
boost::mutex Mmutex;
boost::asio::service Mservice;
boost::asio::thread MserviceThread;
DataStorage Mdata;
}
class MyItemModel:公共QAbstractItemModel{
Q_对象
公众:
void processUpdate(常量更新和更新){
Mservice.post([此,更新](){
增压:锁紧/防护锁(Mlock);
bool willModifyLayout=checkWillModifyLayout(更新)
bool willModifyData=检查willModifyData(更新);
如果(将修改布局){
释放布局BouttoBeChanged();
}
Mdata.processUpdate(更新);
如果(将修改布局){
emit layoutChanged();
}
else if(将修改数据){
emit dataChanged();
}
});
}
虚拟QVariant数据(常量QModelIndex和index,int角色)常量Q_DECL_覆盖{
增压:锁紧/防护锁(Mlock);
if(index.isValid())返回Mdata.data(index,role);
}
私人:
boost::互斥Mmutex;
boost::asio::service Mservice;
boost::asio::thread MserviceThread;
数据存储Mdata;
}
找到了我自己问题的答案:
如果该模型属于不同的QThread,那么该模型的信号将使用Qt::QueuedConnection连接到view,这很好。
但是,如果(默认情况下)模型属于GUI QThread(也称为QCoreApplication::instance()->thread()),则模型的槽将立即执行,导致调用data()、columnCount()等,因此,这是不正常的。
QAbstractItemModel
是
这主要是因为线程中的信号排队
设想以下场景:一个模型包含QList列表代码>(忽略QModelIndex
):
此模型索引无效,因为该模型不再有行
在最好的情况下,它只返回一个无效的QVariant()
在最坏的情况下(没有防御检查),模型尝试访问列表[0]
我认为首先在模型中存储受互斥保护的数据不是一个好主意。视图经常调用模型的data()
方法,即使在缺乏很多并发性的情况下,锁定和解锁互斥锁的开销可能就足够了。如果数据足够重,您可以将其管理封装在一些QObject
中,通过信号将数据更改传达给实际模型,实际模型将存储数据的轻量级“视图”-例如,如果您的数据存储长文本,则模型可以包含每个项的前140个字符,以便在视图中显示它们。对于完整数据的访问(不适用于连接到模型的视图,这样访问就不会经常发生),您可以创建自己的模型API来同步检索数据。这确实是正确的方法。但我使用的框架强制使用boost的asio::io_服务来处理更新。另一方面,如果qabstractemmodel接口函数没有抛出,那么即使是原始代码也可以工作。如果processUpdate()在GUI线程处于data()调用中时发出layoutChanged(),等待锁定互斥锁,则不会发生任何错误。data()将返回一些错误的数据(或者可能是空的QVariant()),将显示它几分钟,然后它将处理信号,并很快开始显示正确的数据。例如,Qt提供了QReadWriteLock。您仍然可以使用互斥锁保护您的模型。当需要读取模型时(例如在data()中),请锁定互斥锁以进行读取访问。当需要更新模型时,锁定互斥锁以进行写访问(这会阻止其他线程同时进行读或写访问)。使用互斥锁的开销可能是一个问题。。。但这并不是这个应用程序所独有的。分析代码以查看是否存在问题。如果没有,请保持简单并使用互斥锁。这本身并不“好”,请参阅是否有方法强制将模型的信号连接为Qt::QueuedConnection
,即使该模型是在与GUI相同的线程上创建的?(模型创建调用模型信号的工作线程)
background thread GUI thread + signal queue, abbreviations for readability
[MODEL] [VIEW] ( )
beginInsertRows(0, 1); ... ( rowsAboutToBeAdded(0, 1) = add(1) )
list << item(); (doing ( add(1) )
endInsertRows(); something ( add(1), rowsAdded(0, 1) = added(1) )
beginRemoveRows(0, 1); else) ( add(1), added(1), rowsAboutToBeRemoved(0, 1) = rem(1) )
list.removeAt(0); ... ( add(1), added(1), rem(1) )
endRemoveRows(0, 1); ... ( add(1), added(1), rem(1), rowsRemoved(0, 1) = rmvd(1) )
rowsAboutToBeAdded(0, 1); ( added(1), rem(1), rmvd(1) )
rowsAdded(0, 1); ( rem(1), rmvd(1) )
possible crash!
model()->data(model()->index(0, 0));