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));