何时使用互斥锁,何时不使用

mad*_*phy 4 c multithreading atomic thread-safety

我有一个名为my_list包含链接列表的全局变量(即,该变量是指向列表的第一个成员的指针)。该变量只能由两个线程(线程 A 和线程 B)编辑。如果列表变空,则该变量将设置为NULL。发生这种情况时,首先将变量设置为NULL,然后释放内存。

由于它是由两个线程编辑的,因此每次线程 A 或线程 B 接触该my_list变量时我都会使用互斥锁。到目前为止没有什么异常。

但是接下来出现了第三个线程,线程 C。该线程永远不会以任何方式接触链表,但它需要不时知道链表是​​否为空。所以,线程C唯一要做的就是

if (my_list) {

    do_something_completely_unrelated();

}
Run Code Online (Sandbox Code Playgroud)

我必须为此使用互斥体吗?我相信这是一个原子操作,因此不需要互斥体。它是否正确?

编辑

我将在这里添加一些背景信息。我想避免使用互斥体的原因是列表很少(总是)更新,但来自线程 C 的检查每隔几毫秒发生一次,因此操作越少越好。

如果列表显示为非NULL则线程 C 会触发使用互斥锁的正确检查,并且如果确认列表不为空,则线程 C 停止其强制检查。

klu*_*utt 5

长话短说

根据该标准,如果至少有一个操作是非原子的,则在写入变量时读取该变量是未定义的行为。

更长的答案

从评论部分来看,您似乎想知道它是否会使您的程序崩溃,或者可能发生的最糟糕的事情是否是您得到错误的值。你的评论,我的重点:

谢谢。但具体会发生什么不好的事情呢?假设该变量正在NULL被线程 A 更改为地址xxxxxx。线程 C 尝试读取它。能得到什么价值?要么NULL要么xxxxxx,两者都可以。我假设程序不会崩溃是错误的吗?

我想说这个简单的检查很可能不会导致你的程序崩溃。检查操作是安全的,因为您很可能会获得一个值。该值可能是错误的,但仅进行检查很可能不会使您的程序崩溃。

然而,这是未定义的行为:

如果程序的执行在不同线程中包含两个冲突的操作,并且至少其中一个不是原子的,并且两者都发生在另一个之前,则该程序的执行就包含数据竞争。任何此类数据竞争都会导致未定义的行为。

C18 标准,第 5.1.2.4 节,第 35 段

然后您发布了以下后续评论:

这实际上并不那么重要,但是我是否正确地假设,如果线程 A 将值从 更改为NULLxxxxxx或反之亦然),我肯定不会得到yyyyyy,但要么 NULL 要么xxxxxx

在这里我想说你的假设是错误的。AFIK,标准中没有任何内容表明指针赋值必须是原子操作,如果是的话我会感到惊讶。正如我上面提到的,这是未定义的行为。

建议的解决方案

使用_Atomic关键字

使用限定符声明列表_Atomic。它有一些限制。例如:

  1. 它不是强制性功能,因此可能会降低可移植性

  2. 它不能与数组一起使用

  3. 如果与结构体一起使用,则无法单独访问该结构体的字段

2 和 3 对你来说应该不重要,因为列表只是一个指针。

在这里阅读_Atomic: https: //en.cppreference.com/w/c/language/atomic

使用不会在每次访问时更新的副本

如果_Atomic这不是一个选项,这里是伪代码的解决方案:

if ( now() - lastUpdated > ms ) // If it was more than ms milliseconds since last update  
    lock(myMutex)
    myListCopy = myList
    unlock(myMutex)
    lastUpdated = now()

if(myListCopy) 
    do_something_completely_unrelated();
Run Code Online (Sandbox Code Playgroud)

lastUpdatedmyListCopy最好是函数的本地变量,但重要的是线程 A 和 B 永远不会接触它们。

  • @LWimsey 找到了。更新了答案。 (2认同)