树人的森林

树人是由森林守护者的自然之力技能创造的,一段时间后将会消失。.................................................一个树人在森林里倒下时,会发出声音吗?...

Imperfect C++:Chapter 11 Statics:11.3Function-Local static objects

By Matthew Wilson

树人 译

11.3Function-Local(局部于函数)的静态对象

  在前两节中我们着眼于非局部静态对象。在这一节中,我们来看看定义在函数作用域中的局部静态对象,例如:

Local &GetLocal()

{

  static Local local;

  return local;

}

  非局部和局部静态对象之间的关键性差别是局部静态对象在需要时被创建,也就是说函数第一次被调用时。此后的调用只是简单地使用已经被创建的实例。当然我们需要某种机制来记录那个被创建的实例,这样一个实现就可以使用一个未见过的初始化标记。

  尽管标准没有指定标记初始化的机制,但暗示还是非常清楚的。Section (C++-98: 6.7;4)提到:“对象在控制流第一次通过它的声明时被初始化;如果初始化因为抛出异常而退出,初始化没有完成,所以在下一次控制流进入声明位置时,会再次尝试初始化。在对象初始化时,如果控制流再次进入声明(递归地),其行为是未定义的。”所以,私底下前面的函数看上去很像下边的:

Listing 11.10.

Local &GetLocal()

{

  static bool  __bLocalInitialized__  = false;

  static byte  __localBytes__[sizeof(Local)];

  if(!__bLocalInitialized__)

  {

      new(__localBytes__) Local();

      __bLocalInitialized__ = true;

  }

  return *reinterpret_cast<Local*>(__localBytes__);

}

  以上情形的问题在于,在多线程环境下会导致一个竞争条件。可能会有多个线程进入并发现__bLocalInitialized__false,接着同时构造local。这会导致一个泄漏或者使进程崩溃,但不论发生什么都不我们想要的。

  有人可能会天真地[10]认为对static声明使用volatile关键可能会有帮助。毕竟,C标准说了(C99-5.1.1.2.3;2)“存取一个volatile对象,更改一个对象,修改一个文件或调用任何一个包含这些操作中任何一个的函数都是有副作用的,也就是改变了执行环境的状态。在执行序列中被指定的某个被称为序列点的位置,在此前估算的所有副作用会全部完成,而且随后的估算不会发生副作用。(附录C中给出的序列点的内容的概要。C++标准对此也说了很多(C++-98:1.9;7)。

[10]这里说的是我自己。哈,亦苦亦甜的记忆

  唉,标准没有说到任何关于线程的事实也意味着我们不能依靠一个实现(编译器)来支持我们的假设。在实践中,volatile的使用与保证对象使用的线程安全性毫无关系。因此,虽然volatile对其它一些东西(Section12.5)偶尔还是有作用的,但volatile本质上是和硬件相关的一种机制。

Imperfection: Function-local static instances of classes with nontrivial constructors are not thread-safe.

11.3.1牺牲延迟计算(lazy-evaluation)

  消除这个风险的一种方法是使用Schwarz计数器来确保在进入多线程模式之前所有的局部静态对象都被初始化掉(警告:不要在任何全局对象构造函数种初始化线程,在Section11.1中提到的)。这是很有效的,但同时它否定了大多数让对象局部化的目的。此外,如果过早调用的话,某些包含了局部静态对象的函数很可能不能恰当地工作;我们可能又回到全局对象的问题上了。

11.3.2救苦救难的自旋互斥量

  局部于线程的对象所固有的竞争条件是非常重要的,所以它不容忽视。但是,它也很少见。我们可以利用这个稀有物种,并使用一种极酷的优雅的解决方案[11],它是基于自旋互斥量的,这些互斥量自身依赖于在第10章中被测过的原子操作。

[11]提示:每次我提到一种极酷的优雅解决方案时,你可以肯定它是我自认为是自己所发明的。

Local &GetLocal()

{

    static int              guard; // Will be zeroed at load time

    spin_mutex              smx(&guard); // Spin on "guard"

    lock_scope<spin_mutex>  lock(smx);   // Scope lock of "smx"

    static Local            local;

    return local;

}

  一切都能工作,那是因为静态的guard变量在进程的零初始化阶段被设定。非静态的spin_mutex对象smx操作guard,而且通过一种lock_scope模板的参数化方法来给自己加锁,非静态的也是一样。所以,测试那些看不见的对于local及其自身的初始化标记的唯一方法就是通过有效的自旋互斥量的防护机制进行防护。

  当然这是由代价的,但如Section10.3所见到的,除非在被防护的段周围有一个高度并行的争用或者被防护的段很长,否则自旋互斥量的代价是非常低的。因为被防护段由一个(看不见的初始化标记的)比较和(返回local的地址的)一个移动组成,所以段本身代价是很低的。And it is hard to conceive of any client code where several threads will be contending for the Local singleton with such frequency that the likelihood of them wasting cycles in the spin will be appreciable.(实在不知如何翻译才好,我的理解是“很难想象任何这样的代码,在其中多个线程以很高(Zzzzz)的频率争用Local单件。”) 所以上边提到的方案是一种保护局部静态对象防止竞争的非常好的解决方案。

阅读更多
个人分类: C++拓展树
想对作者说点什么? 我来说一句

ObjC.pdf官方文档

2011年10月07日 1.15MB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭