C++ 不持有锁的本地静态初始化避免了C+中可能出现的死锁+;11?

C++ 不持有锁的本地静态初始化避免了C+中可能出现的死锁+;11?,c++,concurrency,c++11,static-variables,C++,Concurrency,C++11,Static Variables,本文提出了一种算法,该算法在初始化局部静态变量时不需要保持锁,但仍然使通过变量定义的并发控制流等待初始化完成 该报说,这样做的好处是避免了可能出现的僵局 函数本地静态持续时间对象初始化的核心问题是,包含的函数可能会被并发调用,因此定义可能会并发执行。未能同步可能会引入竞争条件。显而易见的解决方案是同步。问题是,如果同步涉及在初始值设定项执行时持有锁,那么这种同步可能会引入死锁 有人能举个例子说明上面描述的死锁发生在哪里吗 这是经典死锁的简单扩展,适用于编译器提供一个锁的情况 void A2B()

本文提出了一种算法,该算法在初始化局部静态变量时不需要保持锁,但仍然使通过变量定义的并发控制流等待初始化完成

该报说,这样做的好处是避免了可能出现的僵局

函数本地静态持续时间对象初始化的核心问题是,包含的函数可能会被并发调用,因此定义可能会并发执行。未能同步可能会引入竞争条件。显而易见的解决方案是同步。问题是,如果同步涉及在初始值设定项执行时持有锁,那么这种同步可能会引入死锁


有人能举个例子说明上面描述的死锁发生在哪里吗

这是经典死锁的简单扩展,适用于编译器提供一个锁的情况

void A2B() { a.Lock(); B(); a.Unlock(); }
void B() { b.Lock(); ...; b.Unlock(); }
void B2A() { b.Lock(); A();  b.Unlock(); }
void A() { a.Lock(); ...; a.Unlock(); }
如果一个线程调用
A2B()
,而另一个线程调用
B2A()
,则会发生典型的死锁

在静态初始化锁中,编译器提供
b

int A() { a.Lock(); ...; a.Unlock(); return 0; }
void B2A() { static int v = A(); }
void A2B() { a.Lock(); B2A(); a.Unlock(); }
如果假设静态初始化周围有锁,则代码会秘密转换为

void B2A() {
    if (!initialized) {
      b.Lock(); // standard double-check-lock
      if (!initialized) v = A();
      initialized=true;
      b.Unlock();
   }
}

一个线程调用
A2B()
,另一个线程调用
B2A()

如果要在本地静态初始化期间保持锁,则有两种可能的设计:

  • 为每个静态文件分配一个互斥对象
  • 只为所有静态分配一个互斥体
  • 我不是百分之百的肯定,但我相信你提到的引用隐含地假设了设计2。事实上,本文介绍的算法只对所有静态使用一个互斥(代码中称为
    mu


    在design 2下,您将获得死锁,如所述。也就是说,除非在进行初始化时没有实际持有互斥体。这是通过为每个静态设置一个三态标志来实现的,该标志指示以下之一:未初始化、正在初始化、已初始化。并使用全局互斥来设置标志,但在初始化过程中将其解锁。

    此“经典死锁”也称为锁反转。如果我们在初始化
    v
    时不持有锁,如果其他人需要等待
    v
    初始化,我们是否会出现相同的死锁?一个线程调用
    B2A
    并进入初始化。另一个线程调用
    A2B
    并锁定
    a
    并调用
    B2A
    ,需要等待
    v
    的初始化完成。现在第一个线程调用<代码> A<代码>,需要等到<代码> A<代码>被代码< > A2B< /COD>解锁。原来的C++标准避免了这个问题:“如果你调用B2A两次,并且第一个调用仍然忙着初始化<代码> V<代码>,那么第二个调用只是继续未初始化的<代码> V< /代码>。.啊,这是有道理的。文章还说,GCC使用的是易于死锁的算法。链接的答案证明了这一点。谢谢。这个答案刚刚被否决了两次。有人想详细说明一下吗?还是只是一次随机的、毫无意义的驾车经过?我喜欢提供准确的信息。我相信这个答案是准确的。如果需要详细说明,请询问。或者至少在这里留下一条评论,说明否决投票的理由。几个小时前,有人对我的一些答案/问题进行了否决投票。也许是同一个用户?如果系统认为这是滥用否决权,它将不会计算这些否决票。