2011-10-22 36 views
4

在论文http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm中提出了一种算法,该算法在本地静态变量的初始化期间不需要保持锁定,但仍然导致通过变量定义的并发控制流等待直到初始化完成。本地静态初始化没有保持锁定可以避免C++ 11中可能出现死锁?

本文说,这具有避免可能的死锁

的核心问题与功能局部静态持续时间的对象初始化,含功能可以同时调用的优点,并且因此定义可以同时执行。未能同步可能会引入竞争条件。显而易见的解决方案是同步。问题是,如果同步涉及在初始化器执行时持有锁,则此类同步可能会导致死锁。

有人可以请举一个例子来说明上述死锁发生在哪里吗?

回答

4

如果你打算在本地静态初始化期间持有的锁有两种可能的设计:

  1. 分配每静态互斥。
  2. 为所有静态分配一个互斥量。

我不是100%肯定的,但我相信报价以你提到隐含地假定设计2.的确在本文介绍的算法只使用一个互斥体的所有静态(代码名为mu) 。

在设计2下,您遇到this stack overflow answer所述的死锁。也就是说,除非你在初始化时没有真正保存互斥锁。这是通过为每个静态指定一个三态标志来实现的:未初始化,正在初始化,已经初始化。并使用全局互斥锁来设置标志,但在初始化过程中将其解锁。

+0

啊,这是有道理的。该文件还说,GCC使用那种容易出现死锁的算法。链接的答案证明它。谢谢。 –

+0

这个答案刚刚投了两次票。任何人都在关心精心制作?或者它只是一个随机的,无意义的驱动器?我正在提供准确的信息。我相信这个答案是准确的。如果需要阐述,请询问。或者至少在这里留下一个评论,并说明反对票的理由。 –

+0

几个小时前,有人在我的一些答案/问题上有一些downvote的乐趣:http://img40.imageshack.us/img40/5457/snapshot96.png。也许这是同一个用户?如果系统认为它是辱骂性的,那么它就不会计入那些低估。 –

3

这是经典死锁的简单扩展,其中一个锁是编译器提供的。如果一个线程调用A2B()和另一个线程调用B2A()发生

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(); } 

经典的僵局。

在静态初始化锁中,编译器提供了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()

+0

这种“经典死锁”也被称为锁反转。 –

+0

如果我们在初始化'v'时没有保持锁定状态,如果其他人需要等到'v'被初始化,我们才会有相同的死锁吗?一个线程调用'B2A'并进入初始化。另一个线程调用'A2B'并锁定'a'并调用'B2A'并且需要等到'v'的初始化完成。现在第一个线程调用'A',并且需要等到'a'被'A2B'解锁。 –

+0

最初的C++标准通过说“如果你打电话给B2A两次,并且第一次调用仍然忙于尝试初始化'v',那么第二次调用只是继续一个未初始化的'v''来避免这个问题。 –