【问题标题】:global variable multiple reader one writer multithread safe?全局变量多读一写多线程安全吗?
【发布时间】:2018-10-26 10:44:57
【问题描述】:

我正在尝试用 C++ 编写一个程序,其中一个全局变量由多个线程读取并由一个线程更新。在这种情况下,我是否需要编写任何信号量之类的东西,或者我可以保持原样,因为实际上只有 1 个线程正在写入全局变量,因此不存在可能的竞争条件。

我也是信号量的新手,所以如果可能的话,我需要为自己省去麻烦。

程序是这样的: 写线程:不断检查引脚是否有高压,当它为高时设置全局变量

阅读器线程:在无限循环中不断检查全局变量,并在设置时执行一些操作。

【问题讨论】:

    标签: c++ multithreading race-condition


    【解决方案1】:

    这很简单:如果多个线程可以在不同步的情况下同时访问一个对象,并且这些线程中至少有一个正在写入该对象,则程序存在数据竞争。具有数据竞争的程序的行为是未定义的。

    您可以通过使用互斥锁防止同时访问来提供同步,或者在许多情况下,通过使用原子对象来提供同步。

    如果您没有正确同步读取和写入,您可以享受流行的客厅游戏“猜猜这个程序会做什么”。有许多消息线程可以解释为什么在某些情况下数据竞争是可以的。如果您并不真正关心您的程序是否真正正常工作,那很好。如果您确实关心,请同步。

    【讨论】:

    • 好吧,我在这种情况下使用 atomic 会更容易
    • @bakalolo — 是的,这是原子变量的完美候选者。目标硬件的标准库实现者比您或我更了解硬件,并且会做必要的事情以使其正常工作。
    • 如果写入次数不多,原子变量解法更快。如果它会发生很大变化,那么 compareAndSwap(原子操作)会导致很多重试。具有互斥锁的同步解决方案通常只允许一个线程访问变量。在其他编程语言中,有读写器锁,并且可能在 c++ 的标准库中也可用。可以使用互斥锁构建读写器锁(但不要这样做:-))
    • @thomas -- 我看到 C++17 中添加了std::shared_mutex,表示它可以用于读多写少的情况。
    • 那是读写锁。您具有锁定/解锁功能以及 lock_shared/unlock_shared 功能。共享意味着多个线程可以访问受保护的资源/变量
    【解决方案2】:

    如果不知道在您的特定情况下什么会构成一个正确程序,这很难说。但确实,只要您只有一个并发写入器,您就不必担心损坏内存。

    不过,多个读取器将在不锁定的情况下获得不确定的值。您肯定会希望对值使用 atomic loads (seq_cst, most likely) and stores,并且您可能需要查看 volatile 关键字以防止在您的应用程序中将值存储在寄存器中。

    【讨论】:

    • volatile 在这里绝对帮不上忙。
    • @Pavel - 我不知道你为什么这么说。如果全局变量是一个裸 int,那么将它传递给一个接受 volatile int 引用的函数就是重新加载值和缓存它之间的区别。请参阅此godbolt 了解几种不同的情况。
    • 根据 OP 的要求,使用 volatile 似乎是可以的(但根本不涉及线程安全)。但是,volatile 绝不会提供线程安全。即使是常规 atomic 也可能无法提供,这取决于您与 atomics 一起使用的 memory_order
    • 对不起,我没有说为什么它“绝对显示”,而实际上你发布的代码显示相反,我最初没有审查 asm :) x86 具有强大的内存模型,所以它恰好是使用 volatile 时生成的代码相同。例如,在 ARM 上,它绝对不会一样。将 ARM 编译器添加到您的示例中,您会看到。
    【解决方案3】:

    您需要提供更多上下文,但通常您应该使用某种同步原语(如std::mutex)来保护对该变量的访问。如果您不在乎某些线程是否读取不正确的值,您可以放宽要求,但这完全取决于您的用例。

    您可以通过“单个阅读器”来简化和替换您的问题中的“多个阅读器”,答案仍然是:您必须保护不同线程对共享变量的访问,否则阅读器线程可能无法“观察”编写器线程所做的更改,本质上最终会读取和使用错误的值。

    如果您尝试从线程中读取/修改它是一个简单的整数,那么您可以使用std::atomic

    【讨论】:

    • 所以你说互斥锁是可选的但原子是强制性的?不,我不在乎有时值是否错误
    • 如果您不关心正确性,那么您可以简单地使用 volatile 说明符,但在这种情况下,“阅读器”线程在一段时间内不会“看到”更新的值。没有一些示例程序或上下文很难说。
    • 我在我的新帖子中做了一些上下文,然后你可以告诉我
    • 对于您的情况,您可以使用 std::atomic,如果您不关心读者可能看不到立即更新的值,您也可以简单地使用 volatile。您可以简单地使用 std::mutex 并使其真正线程安全。