Multithreading std::call_once和函数级静态初始化之间的区别是什么

Multithreading std::call_once和函数级静态初始化之间的区别是什么,multithreading,c++11,Multithreading,C++11,1) std::给你打一次电话 A a; std::once_flag once; void f ( ) { call_once ( once, [ ] { a = A {....}; } ); } 2) 功能级静态 A a; void f ( ) { static bool b = ( [ ] { a = A {....}; } ( ), true ); } 这两个代码段具有相同的行为,即使在初始化过程中出现异常也是如此 此结论基于(我对)c++11标准(草案n3337

1) std::给你打一次电话

A a;
std::once_flag once;

void f ( ) {
    call_once ( once, [ ] { a = A {....}; } );
}
2) 功能级静态

A a;

void f ( ) {
    static bool b = ( [ ] { a = A {....}; } ( ), true );
}

这两个代码段具有相同的行为,即使在初始化过程中出现异常也是如此

此结论基于(我对)c++11标准(草案n3337)的以下引用:

  • 1第6.7节声明声明第4条规定:
具有静态存储持续时间(3.7.1)或线程存储持续时间(3.7.2)的所有块作用域变量的零初始化(8.5)在进行任何其他初始化之前执行。具有静态存储持续时间的块范围实体的恒定初始化(3.6.2)(如适用)在首次输入其块之前执行。在与允许实现在命名空间范围(3.6.2)中静态初始化具有静态或线程存储持续时间的变量相同的条件下,允许实现执行具有静态或线程存储持续时间的其他块范围变量的早期初始化。否则,当控件第一次通过其声明时,该变量被初始化;此类变量在初始化完成时被视为已初始化。如果初始化通过抛出异常退出,则初始化未完成,因此下次控件进入声明时将再次尝试初始化。如果控件在初始化变量时并发输入声明,则并发执行应等待初始化完成。88如果控件在初始化变量时递归重新输入声明,则行为未定义

这意味着:

void f ( ) {
    static bool b = ( [ ] { a = A {....}; } ( ), true );
}
void f ( ) {
    call_once ( once, [ ] { a = A {....}; } );
b
保证只初始化一次,意味着lambda只执行(成功)一次,意味着
a=a{…}仅执行(成功)一次

  • 2第30.4.4.2节函数调用一次说明:
不调用func的call_once执行是被动执行。调用其func的call_once的执行是活动执行。活动执行应调用INVOKE(decage\u COPY(std::forward(func))、decage\u COPY(std::forward(args))…)。如果对func的此类调用引发异常,则执行异常,否则返回异常。异常执行应将异常传播给call_的调用方一次在任何给定的once_标志的所有call_once执行中:最多一个是返回执行如果有返回执行,则应为最后一次活动执行;只有当有返回执行时,才会有被动执行

这意味着:

void f ( ) {
    static bool b = ( [ ] { a = A {....}; } ( ), true );
}
void f ( ) {
    call_once ( once, [ ] { a = A {....}; } );
std::call_once
的lambda参数仅执行(成功)一次,意思是
a=a{…}仅执行(成功)一次


在这两种情况下,
a=a{…}仅执行(成功)一次。

对于您的示例用法,hmjd的回答充分解释了没有区别(除了在
call\u once
案例中需要额外的全局
once\u标志
对象)。但是,
call\u once
案例更灵活,因为
once\u标志
对象没有绑定到单个作用域。例如,它可以是一个类成员,并可由多个函数使用:

class X {
  std::once_flag once;

  void doSomething() {
    std::call_once(once, []{ /* init ...*/ });
    // ...
  }

  void doSomethingElse() {
    std::call_once(once, []{ /*alternative init ...*/ });
    // ...
  }
};
现在,根据首先调用哪个成员函数,初始化代码可能会有所不同(但对象仍将只初始化一次)


因此,对于简单的情况,局部静态可以很好地工作(如果编译器支持的话),但是有一些不太常见的用法可能更容易用
call\u once

实现。您的两个示例并不相同,
call\u once
忽略您传递给它的可调用对象的返回值。您可能希望在两个lambda表达式中都分配给
a
。是的,我忘了更改第一个表达式。fixedIt可能不必要地迂腐,但我认为标准语言(至少引用的部分-可能还有其他章节对此进行了更详细的说明)并不一定意味着lambda将只执行一次,而只是计算结果将只分配给变量一次。防止并行执行的内容的粒度可能未指定或由实现定义。