QSettings 可以被多个线程安全地访问吗?

Can QSettings be safely accessed by multiple threads?

提问人:Dazckel 提问时间:11/14/2023 最后编辑:Abderrahmene Rayene MihoubDazckel 更新时间:11/15/2023 访问量:74

问:

我正在用Qt Framework编写一个多线程项目。我最近开始使用动态分析工具,如 helgrind 和螺纹消毒剂来检查螺纹安全性。

在整个应用程序中,我一直在编写一个名为QSettings的Qt类型。此类的名义功能是在文件或注册表中存储应用程序设置,例如窗口大小、公司名称和其他类型的设置(控制器的增益、计时器超时等)。

从文档中我们读到:

同时从多个线程或进程访问设置

QSettings 是可重入的。这意味着您可以同时在不同的线程中使用不同的 QSettings 对象。即使 QSettings 对象引用磁盘上的相同文件(或系统注册表中的相同条目),此保证也有效。如果通过一个 QSettings 对象修改设置,则该更改将立即在位于同一位置且位于同一进程中的任何其他 QSettings 对象中可见。

同样从文档中我们知道只有一个函数是线程安全的

最后,我想记住以下部分:

无效 QSettings::sync()

将任何未保存的更改写入永久存储,并重新加载其他应用程序在此期间更改的任何设置。 此函数由 QSettings 的析构函数自动调用,并由事件循环定期调用,因此您通常不需要自己调用它。

和:

无效 QSettings::setAtomicSyncRequired(bool enable)

配置是否需要 QSettings 来执行原子保存和重新加载(同步)设置。如果 enable 参数为 true(默认值),则 sync() 将仅执行原子同步操作。如果无法做到这一点,sync() 将失败,status() 将是一个错误条件。 将此属性设置为 false 将允许 QSettings 直接写入配置文件,并忽略任何试图将其锁定到同时尝试写入的其他进程的错误。由于存在损坏的可能性,因此应谨慎使用此选项,但在某些情况下是必需的,例如存在于其他不可写目录中的 QSettings::IniFormat 配置文件或 NTFS 备用数据流。

因此,原则上 QSettings 是可重入的,不能在多线程应用程序中安全地使用,但如果我们将 setAtomicSyncRrequired 设置为 True,则可以完成。

我从文档中了解到的。

QSettings 可以从不同的线程实例化,并以隔离的方式使用,因此每个线程都有一个设置的私有副本。每隔一段时间,sync() 就会被调用,并且所有线程都以安全的方式获得相同的状态设置,当且仅当 setAtomicSyncRequired 设置为 True 时。如果 setAtomicSyncRequired 设置为 true,则以原子方式对文件进行写入。所以这是为了以一种好的方式写作,不是吗?如果我们使用 set(value),那么我们修改了 Qsettings 的本地副本,但由于 sync() 写入公共文件,线程最终将具有相同的设置状态。

  1. 问题:这是正确的解释吗?

当我开始使用 helgrind 和线程清理器时,由于不同的线程写入同一文件,它们给我带来了数据竞争问题。被直接访问的文件是存储 QSettings 设置的配置文件。

  1. 问题:这是误报吗?

  2. 问题:我使用 QSettings 有问题吗?如何改进它才能获得一种方法来存储应用程序中所有线程的通用设置?

  3. 问题:只有一个指向同一 QSettings 对象的指针并设置为 true setAtomicSyncRequired,以便通过 sync() 每隔一小段时间同步所有线程设置是否是一个好主意?

C++ Qt 线程安全 QSonsets

评论


答:

3赞 Homer512 11/14/2023 #1

我理解是在多个进程之间,而不是多个线程之间。在 Linux 上,设置存储在单个文件中,因此您需要以原子方式写入它。QSaveFile为您完成此操作。在 Windows 上,写入注册表项本身已经是线程安全的,但写入大量注册表项可能需要进一步锁定。sync

从源代码来看,我似乎很清楚Qt总是试图以原子方式编写。只是根据文件系统的不同,如果锁定文件不起作用,这可能会失败。然后,如果为 false,则写入设置将继续,没有原子性,否则写入设置将失败。请参阅链接文件中的第 1373 行:setAtomicSyncRequired

void QConfFileSettingsPrivate::syncConfFile(QConfFile *confFile)
{
…
    /*
        Use a lockfile in order to protect us against other QSettings instances
        trying to write the same settings at the same time.

        We only need to lock if we are actually writing as only concurrent writes are a problem.
        Concurrent read and write are not a problem because the writing operation is atomic.
    */
    QLockFile lockFile(lockFileName);
    if (!readOnly && !lockFile.lock() && atomicSyncOnly) {
        setStatus(QSettings::AccessError);
        return;
    }
…
}

QSettings它们受每个 INI 文件的互斥锁和一个全局互斥锁保护。我认为可以安全地假设所有操作都是线程安全的。这符合我对Qt的期望。 只需要将数据发布到其他进程,而不需要将数据发布到同一进程中的其他线程。sync

当我开始使用 helgrind 和线程清理器时,由于不同的线程写入同一文件,它们给我带来了数据竞争问题。被直接访问的文件是存储 QSettings 设置的配置文件。

问:这是误报吗?

是的,我认为这是假阳性。清理程序可能不了解锁定文件和配置文件之间的关系。

问:我使用 Qsettings 有问题吗?如何改进它才能获得一种方法来存储应用程序中所有线程的通用设置?

我不认为有什么问题。但是,要小心原子性。加载和保存单个设置是原子的,但访问一组设置不是原子的。例如,如果一个线程更改了两个设置,而另一个线程读取这两个设置,则读取线程可能会获得一个旧值和一个新值。

问题:只有一个指向同一 QSetting 对象的指针并设置为 true setAtomicSyncRequired,以便通过 sync() 每隔一小段时间同步所有线程设置是否是一个好主意?

不,我不认为这是一个好主意,如果有的话,情况更糟。QSettings::beginGroup 等操作,除非添加一些外部锁定,否则将无法正常工作。endGroupbeginArrayendArray

该设计旨在使对象本身是线程或父对象的本地对象,并且可能生存期较短。你的使用很好。QSettings

至于同步,请注意,默认值为 true,并由析构函数自动调用。isAtomicSyncRequiredsync()QSettings