跨多个线程和 cpu 安全地修改和读取布尔值的选项都有哪些?

Posted

技术标签:

【中文标题】跨多个线程和 cpu 安全地修改和读取布尔值的选项都有哪些?【英文标题】:What are the options for safely modifying and reading a boolean across multiple threads and cpus?跨多个线程和 cpu 安全地修改和读取布尔值的选项有哪些? 【发布时间】:2021-11-10 19:57:24 【问题描述】:

我有一些 C++ 代码大致包含这个逻辑:

class wrapper_info 
public:
        bool isConnected();
        void connectedHandler();
        void disconnectedHandler();
protected:
        bool _connected;


void wrapper_info::connectedHandler() 
        _connected = true;


void wrapper_info::disconnectedHandler() 
        _connected = false;


bool wrapper_info::isConnected() 
        return _connected;


extern "C"
bool is_connected(void *obj) 
        wrapper_info *wrapper_obj = reinterpret_cast<wrapper_info*>(obj);
        return wrapper_obj->isConnected();


由于我无法控制的原因,不同的线程(在不同的 CPU 内核上运行)以下列方式调用这些函数。

线程 1、2、3is_connected(obj)

线程 2:连接启动时connectedHandler()

线程 3 disconnectedHandler() 连接断开时。

我认为如果重复调用connectedHandler()disconnectedHandler() 可能会出现问题,两个线程写入_connected 时出现问题并且写入出现故障,从而导致最终值错误。轮询_connected 也可能存在问题。

我的问题是:

    单独的线程轮询和修改_connected 的值实际上会产生哪些潜在问题? 有哪些选项可以防止这些情况发生?也许将_connected 设置为volatile bool 可能会解决轮询值的问题。我还在考虑线程 2 和 3 修改其值的问题,也许将其设为原子 bool 并使用原子集操作将足以防止诸如乱序内存操作之类的问题。我也知道其他潜在的解决方案是锁或内存屏障,如 smb_mb。但是,我不确定应该使用什么。

非常感谢。

【问题讨论】:

使其成为原子 bool 并使用原子集操作将足以防止诸如乱序内存操作之类的问题 是的,这样做。 volatile 不是线程同步技术。 使用 std::atomic,或者如果您需要做的不仅仅是设置一个布尔值,请使用 std::unique_lock 和 std::mutex。但你是对的,你必须做点什么 Is it ok to read a shared boolean flag without locking it when another thread may set it (at most once)? 仅供参考,没有 C/C++ 代码之类的东西。您的代码使用 class 关键字,因此它是 C++ 而不是 C 语言。我强烈建议不要混合这两种语言,这会使您的程序更复杂,增加更多缺陷并且更难维护。 【参考方案1】:

单独的线程轮询和修改 _connected 的值实际上会产生哪些潜在问题?

无论如何,这是未定义的行为。

有什么办法可以防止这些?

一个常见的解决方案是使用std::atomic&lt;bool&gt; 而不是bool

有一些更奇特(也更复杂)的方法来确保线程之间的同步,但std::atomic 是一个很好的首选,而且正确使用也不难。

也许将_connected 设为volatile bool 可能会解决问题

不会的。 volatile does not solve thread synchronization issues.

【讨论】:

谢谢。我没有同步其他任何东西,只是避免未定义的行为并确保没有错误,所以我想将 _connected 声明为std::atomic&lt;bool&gt; _connected 就足以使用_connected.store(true/false, std::memory_order_relaxed)_connected.load(std::memory_order_relaxed)?我想我不需要其他内存订单,因为我不关心线程在修改 _connected 之前看到其他事情已经发生(没有其他事情发生)。这是正确的逻辑吗?如果有帮助,这将在 linux x86_64 上运行 为了详细说明以防我没有意义,我的理解是在线程1中的int val = 0; _connected.store(true, memory_order_release)和线程2中的_connected.load(memory_order_acquire); int x = val;这样的情况下使用其他内存顺序,例如memory_order_release和memory_order_acquire(确保 x 为 0)。但是,我没有任何其他依赖项,所以我认为我可以使用轻松。 @someoneserious 如果您有新问题,我推荐使用 [] 按钮。你的问题会比这里的 cmets 得到更多的关注。我认为这是一个很好的问题。 谢谢,我会这样做的

以上是关于跨多个线程和 cpu 安全地修改和读取布尔值的选项都有哪些?的主要内容,如果未能解决你的问题,请参考以下文章

C 读取和线程安全 (linux)

C ++如果一个线程写入一旦完成就会切换一个布尔值,那么在另一个线程的循环中读取该布尔值是不是安全?

面试官:如何安全地使用List

QObject 重入和线程安全

Java 高并发三 Java内存模型和线程安全详解

Java 中的 volatile int 是线程安全的吗?