【问题标题】:Is this code thread-safe?这段代码是线程安全的吗?
【发布时间】:2011-05-18 12:20:45
【问题描述】:

假设我们有一个线程安全的比较和交换函数,例如
long CAS(long * Dest ,long Val ,long Cmp)
比较 DestCmp,如果比较成功,则将 Val 复制到 Dest 并以原子方式返回 Dest 的原始值。

所以我想问问你下面的代码是否是线程安全的。

while(true)
{
    long dummy = *DestVar;
    if(dummy == CAS(DestVar,Value,dummy) )
     {
        break;
    }

}

编辑:
Dest 和 Val 参数是指向在堆上创建的变量的指针。 InterlockedCompareExchange 是 CAS 函数的一个例子。

【问题讨论】:

  • 这是 C# 还是 C++?请选择一个。
  • 当然可以。一个是本地的,另一个不是。他们对不同操作的原子性有不同的承诺。
  • @Oded:CAS 的重点是确保考虑原子性。
  • 此外,C++ 没有任何原子性承诺

标签: c++ multithreading concurrency thread-safety


【解决方案1】:

编辑。对问题的编辑意味着大部分内容都不相关。尽管如此,我还是会保留这一点,因为 C# 案例中的所有问题也会转移到 C++ 案例中,但 C++ 案例会带来更多的问题,因此并非完全不相关。


是的,但是……

假设您的意思是这个 CAS 是原子的(C# Interlocked.CompareExchange 就是这种情况,并且有些东西可以在某些 C++ 库中使用),它本身就是线程安全的。

但是DestVar = Value 本身也可能是线程安全的(它将在 C# 中,是否在 C++ 中取决于实现)。

在 C# 中,对整数的写入保证是原子的。因此,执行DestVar = Value 不会因为另一个线程中发生的事情而失败。它是“线程安全的”。

在 C++ 中没有这样的保证,但在某些处理器上是有的(事实上,让我们暂时放弃 C++,当涉及到 C# 的更强保证时,已经足够复杂了,而 C++ 具有所有这些复杂性等等当涉及到这类问题时)。

现在,原子 CAS 操作本身的使用将始终是“头部安全”的,但这并不是线程安全的复杂性所在。重要的是操作组合的线程安全。

在您的代码中,在每个循环中,值将被原子覆盖,或者不会。在它不会的情况下,它会再试一次,直到它成功为止。它可能最终会旋转一段时间,但最终会起作用。

这样做会产生与简单赋值完全相同的效果 - 包括可能会弄乱另一个线程中发生的事情并导致严重的线程相关错误。

看一下Is this use of a static queue thread-safe? 的答案以及它的工作原理的解释,以便进行比较。请注意,在每种情况下,CAS 要么被允许失败,因为它的失败意味着另一个线程做了一些“有用的”事情,或者当它被检查成功时,所做的不仅仅是停止循环。这是 CAS 的组合,每个都注意由其他操作引起的可能状态,从而允许线程安全的无锁无等待代码。

现在我们已经完成了,还请注意,您不能将其直接移植到 C++(它依赖于垃圾收集来使一些可能的 ABA 场景产生一些影响不大的情况,在 C++ 中存在可能存在内存的情况泄漏)。你说的是哪种语言也很重要。

【讨论】:

  • +1 以获得长而解释性的答案。 :) 所以使用Interlocked.CompareExhange 代码在C# 中是线程安全的。但是即使我们确定 CAS 函数是线程安全的,对 C++ 的情况有什么顾虑呢?
  • 我对自己的解释一定没有我希望的那么好。使用 Interlocked.CompareExchange 意味着这 5 行是线程安全的,但您的程序作为一个整体不会。事实上,对于 C#,它们相当于一个简单的赋值。将单个简单调用称为线程安全是没有意义的,您必须从更大的操作集的角度来考虑它。
【解决方案2】:

对于任何环境,这是不可能的。您没有定义以下内容:

  • DestVarValue 的内存位置是什么?在堆上还是在栈上?如果它们在堆栈上,那么它是线程安全的,因为没有其他线程可以访问该内存位置。

  • 如果DestVarValue 在堆上,那么它们是引用类型还是值类型(具有赋值语义复制)。如果是后者,那么它是线程安全的。

  • CAS 是否同步访问自身?换句话说,它是否有某种互斥结构,一次只允许一个调用?如果是这样,那么它是线程安全的。

  • 如果上面提到的任何条件不成立,那么这是否都是线程安全的不确定。有了关于上述条件的更多信息(以及这是 C++ 还是 C#,是的,这很重要)可以提供答案。

【讨论】:

    【解决方案3】:

    实际上,这段代码有点破。要么您需要知道编译器如何读取 *DestVar(在 CAS 之前或之后),这具有完全不同的语义,要么您试图在 *DestVar 上旋转,直到其他线程更改它。当然不是前者,因为那太疯狂了。如果是后者,那么您应该使用您的原始代码。就目前而言,您的修订版不是线程安全的,因为它根本不安全。

    【讨论】:

      猜你喜欢
      • 2013-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-16
      • 1970-01-01
      相关资源
      最近更新 更多