多线程局部静态变量的初始化,需不需要同步

时间:2021-04-05 18:09:07
一个函数内部的静态类对象,因为静态变量是第一次使用的时候初始化的,如果多线程调用这个函数,静态变量初始化会不会出问题?

23 个解决方案

#1


反汇编了一下,静态变量的原理其实就是编译器在自己声明了一个BOOL型变量,此变量初始值是 0 ,编译器生成代码里面会检测这个BOOL变量,如果为零就调用变量的构造函数,如果不为零就不调用,一旦初始化完成,这个BOOL变量会被置为 1 .

编译器不可能自己生成同步代码,所以,应该是需要手动同步。

#2


按标准来说,会
你可以学boost,在干正事前先做好

#3


一个函数内部的静态类对象别的线程访问不到,应该不会有问题

#4


引用 3 楼 worldy 的回复:
一个函数内部的静态类对象别的线程访问不到,应该不会有问题

假设有2个线程在同一时刻调用这个函数呢

#5


引用 4 楼 nieyanbing19870820 的回复:
Quote: 引用 3 楼 worldy 的回复:

一个函数内部的静态类对象别的线程访问不到,应该不会有问题

假设有2个线程在同一时刻调用这个函数呢


那这样,这样确实需要同步;线程函数最好别使用静态变量,这样对函数可重入伤害很大

#6


局部静态变量的初始化是安全的,如果编译器有Bug除外。

如果不放心,就改成全局的吧,让它提前初始化。

#7


pthread_once+static object *+new.

#8


引用 6 楼 u010936098 的回复:
局部静态变量的初始化是安全的,如果编译器有Bug除外。

如果不放心,就改成全局的吧,让它提前初始化。


这是标准里的吗?

全局的更不安全,全局静态变量最大的问题是不知道什么时候初始化,或者说初始化非常早,如果有多个静态变量,而且他们是相关的,无法保证使用到的一定已经初始化,静态变量可能仅仅是保证在main函数调用前已经初始化完成。

#9


如果说:局部静态变量的初始化是安全的,多线程调用,编译器怎么保证同步?这不是编译器能做的了的,同步必须用同步机制。

#10


http://www.cppblog.com/lymons/archive/2010/08/01/120638.html

明确的说,不是线程安全的。

#11


If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization

太多的静态的、需要初始化代码的、初始化代码彼此相关的对象总是很麻烦的。这种对象越少越好,而且构造函数要最小化。

#12


楼上说的是C++11,03中还没有加入这段文字。

以前的标准都没有规定局部静态变量的初始化在并发模式下是否安全,于是编译器们纷纷偷懒,完全不去处理它的并发安全问题。

因此,多线程程序最好不要用需要明显初始化的局部静态变量,或者给构造函数加锁,构造函数中检查并设置初始化标置,依据标置决定是否需要进行初始化。不要担心加锁会影响效率,因为只有最初调用该函数的时候才会执行这段构造函数。构造成功后,系统不会再进行对象构造。系统判断静态对象是否已经构造的代码和你加锁并初始化的代码正好构成一个“双检测锁定”。

如果这个类有不可重复初始化的基类、不可重复初始化的成员对象,或这个类还会在其它地方以非静态的方式大量使用,那么使用指针。写一个新类把它包装起来,其中一个成员是原来那个类的指针类型,新类的构造函数加锁,然后判断指针是否为空(静态变量初始值必然是全0),并在堆中创建对象。

注意加锁时最好用命名的互斥量,以免不同线程进入函数时各创建一个不同的锁,导致锁定失去作用。

#13


复杂对象的静态全局变量自动初始化,几乎一定会出问题,因为初始化的时候往往运行环境还没有建立起来,这就是我用局部静态变量的原因。初始化就是调用一下这个函数就行,之后再进行多线程的任务,这样可以避免处理同步问题。

#14


引用 11 楼 u010936098 的回复:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization

太多的静态的、需要初始化代码的、初始化代码彼此相关的对象总是很麻烦的。这种对象越少越好,而且构造函数要最小化。


标准规定的等待初始化完成,这个编译器怎么做得到?

#15


有静态变量或者使用了全局变量的函数都是不可重入的. 不是线程安全的.
要在多线程下使用这些函数就要在函数的外面加锁.

#16


当我们声明一个静态局部变量时,系统本身就会创建一个附加的静态变量,用来标示静态变量是否被初始化,并且在进入函数之后的代码中做了一些附加的事。

要让它实现“初始化时其它进入的线程等待”,只要在这些附加的事中再加入一些代码就可以了。比如读写锁、双检测锁定、原子操作的标志加Event等等,由编译器去选择代价最低的手段就行了。

#17


引用 16 楼 u010936098 的回复:
当我们声明一个静态局部变量时,系统本身就会创建一个附加的静态变量,用来标示静态变量是否被初始化,并且在进入函数之后的代码中做了一些附加的事。

要让它实现“初始化时其它进入的线程等待”,只要在这些附加的事中再加入一些代码就可以了。比如读写锁、双检测锁定、原子操作的标志加Event等等,由编译器去选择代价最低的手段就行了。

我觉得,你说的这些方式都不是编译器应该做的,这会干扰到用户自己的同步,编译怎么识别用户自己是否已经有同步代码?

编译器能做的,是一些通用规定,调整代码的位置,自动生成一些特定的代码,比如增加一个bool变量,这都不会干扰到用户的代码。总之要么用户明确的知道编译器做了什么,比如构造析构函数,要么无需知道编译器做了什么,比如给成员函数传递this指针参数。但是不应该生成过于复杂的自动代码。

事实上,我觉得静态全局变量的自动初始化,就有点不可控,不是一个非常好的特性。

#18


用户自己的同步代码不应该也没有能力去控制静态对象的初始化,只能去控制静态对象的访问,所以不会冲突。

而正因为用户没有办法为静态变量的初始化过程增加同步控制,这一语言特性是很重要的——除非决定将静态变量列为“兼容但会被放弃”的语言内容。

当然你也可以选择在构造函数中加锁,但编译器不需要去理睬你的锁,这个锁将注定无法起到效果——编译器已经保证不会有多个线程同时进入这个构造函数中。

#19


需要同步,因为有竞争,可以使用tsl或者以下方法

void foo()
{
  static std::string* pConfig = 0;
  if(!p)
  {
    lock theLock;
    if(!p)
    {
      static std::string config = "hello";
      pConfig = &config;
    }
  }
  //...
  //pConfig is correctly created
}

#20


static volatile std::string* pConfig = 0;

引用 19 楼 mujiok2003 的回复:
需要同步,因为有竞争,可以使用tsl或者以下方法

void foo()
{
  static std::string* pConfig = 0;
  if(!p)
  {
    lock theLock;
    if(!p)
    {
      static std::string config = "hello";
      pConfig = &config;
    }
  }
  //...
  //pConfig is correctly created
}

#21


会出问题,尚未初始化时,多线程竞速初始化,可能初始化多次。
解决办法:在子线程创建前,在主线程中调用一次以完成初始化。
你的加锁办法有损效率。

#22


引用 21 楼 proad 的回复:
会出问题,尚未初始化时,多线程竞速初始化,可能初始化多次。
解决办法:在子线程创建前,在主线程中调用一次以完成初始化。
你的加锁办法有损效率。

延迟初始化也有好处的. 如果从未被使用,则不需初始化.

#23


C++0X以后,要求编译器保证内部静态变量的线程安全性

#1


反汇编了一下,静态变量的原理其实就是编译器在自己声明了一个BOOL型变量,此变量初始值是 0 ,编译器生成代码里面会检测这个BOOL变量,如果为零就调用变量的构造函数,如果不为零就不调用,一旦初始化完成,这个BOOL变量会被置为 1 .

编译器不可能自己生成同步代码,所以,应该是需要手动同步。

#2


按标准来说,会
你可以学boost,在干正事前先做好

#3


一个函数内部的静态类对象别的线程访问不到,应该不会有问题

#4


引用 3 楼 worldy 的回复:
一个函数内部的静态类对象别的线程访问不到,应该不会有问题

假设有2个线程在同一时刻调用这个函数呢

#5


引用 4 楼 nieyanbing19870820 的回复:
Quote: 引用 3 楼 worldy 的回复:

一个函数内部的静态类对象别的线程访问不到,应该不会有问题

假设有2个线程在同一时刻调用这个函数呢


那这样,这样确实需要同步;线程函数最好别使用静态变量,这样对函数可重入伤害很大

#6


局部静态变量的初始化是安全的,如果编译器有Bug除外。

如果不放心,就改成全局的吧,让它提前初始化。

#7


pthread_once+static object *+new.

#8


引用 6 楼 u010936098 的回复:
局部静态变量的初始化是安全的,如果编译器有Bug除外。

如果不放心,就改成全局的吧,让它提前初始化。


这是标准里的吗?

全局的更不安全,全局静态变量最大的问题是不知道什么时候初始化,或者说初始化非常早,如果有多个静态变量,而且他们是相关的,无法保证使用到的一定已经初始化,静态变量可能仅仅是保证在main函数调用前已经初始化完成。

#9


如果说:局部静态变量的初始化是安全的,多线程调用,编译器怎么保证同步?这不是编译器能做的了的,同步必须用同步机制。

#10


http://www.cppblog.com/lymons/archive/2010/08/01/120638.html

明确的说,不是线程安全的。

#11


If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization

太多的静态的、需要初始化代码的、初始化代码彼此相关的对象总是很麻烦的。这种对象越少越好,而且构造函数要最小化。

#12


楼上说的是C++11,03中还没有加入这段文字。

以前的标准都没有规定局部静态变量的初始化在并发模式下是否安全,于是编译器们纷纷偷懒,完全不去处理它的并发安全问题。

因此,多线程程序最好不要用需要明显初始化的局部静态变量,或者给构造函数加锁,构造函数中检查并设置初始化标置,依据标置决定是否需要进行初始化。不要担心加锁会影响效率,因为只有最初调用该函数的时候才会执行这段构造函数。构造成功后,系统不会再进行对象构造。系统判断静态对象是否已经构造的代码和你加锁并初始化的代码正好构成一个“双检测锁定”。

如果这个类有不可重复初始化的基类、不可重复初始化的成员对象,或这个类还会在其它地方以非静态的方式大量使用,那么使用指针。写一个新类把它包装起来,其中一个成员是原来那个类的指针类型,新类的构造函数加锁,然后判断指针是否为空(静态变量初始值必然是全0),并在堆中创建对象。

注意加锁时最好用命名的互斥量,以免不同线程进入函数时各创建一个不同的锁,导致锁定失去作用。

#13


复杂对象的静态全局变量自动初始化,几乎一定会出问题,因为初始化的时候往往运行环境还没有建立起来,这就是我用局部静态变量的原因。初始化就是调用一下这个函数就行,之后再进行多线程的任务,这样可以避免处理同步问题。

#14


引用 11 楼 u010936098 的回复:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization

太多的静态的、需要初始化代码的、初始化代码彼此相关的对象总是很麻烦的。这种对象越少越好,而且构造函数要最小化。


标准规定的等待初始化完成,这个编译器怎么做得到?

#15


有静态变量或者使用了全局变量的函数都是不可重入的. 不是线程安全的.
要在多线程下使用这些函数就要在函数的外面加锁.

#16


当我们声明一个静态局部变量时,系统本身就会创建一个附加的静态变量,用来标示静态变量是否被初始化,并且在进入函数之后的代码中做了一些附加的事。

要让它实现“初始化时其它进入的线程等待”,只要在这些附加的事中再加入一些代码就可以了。比如读写锁、双检测锁定、原子操作的标志加Event等等,由编译器去选择代价最低的手段就行了。

#17


引用 16 楼 u010936098 的回复:
当我们声明一个静态局部变量时,系统本身就会创建一个附加的静态变量,用来标示静态变量是否被初始化,并且在进入函数之后的代码中做了一些附加的事。

要让它实现“初始化时其它进入的线程等待”,只要在这些附加的事中再加入一些代码就可以了。比如读写锁、双检测锁定、原子操作的标志加Event等等,由编译器去选择代价最低的手段就行了。

我觉得,你说的这些方式都不是编译器应该做的,这会干扰到用户自己的同步,编译怎么识别用户自己是否已经有同步代码?

编译器能做的,是一些通用规定,调整代码的位置,自动生成一些特定的代码,比如增加一个bool变量,这都不会干扰到用户的代码。总之要么用户明确的知道编译器做了什么,比如构造析构函数,要么无需知道编译器做了什么,比如给成员函数传递this指针参数。但是不应该生成过于复杂的自动代码。

事实上,我觉得静态全局变量的自动初始化,就有点不可控,不是一个非常好的特性。

#18


用户自己的同步代码不应该也没有能力去控制静态对象的初始化,只能去控制静态对象的访问,所以不会冲突。

而正因为用户没有办法为静态变量的初始化过程增加同步控制,这一语言特性是很重要的——除非决定将静态变量列为“兼容但会被放弃”的语言内容。

当然你也可以选择在构造函数中加锁,但编译器不需要去理睬你的锁,这个锁将注定无法起到效果——编译器已经保证不会有多个线程同时进入这个构造函数中。

#19


需要同步,因为有竞争,可以使用tsl或者以下方法

void foo()
{
  static std::string* pConfig = 0;
  if(!p)
  {
    lock theLock;
    if(!p)
    {
      static std::string config = "hello";
      pConfig = &config;
    }
  }
  //...
  //pConfig is correctly created
}

#20


static volatile std::string* pConfig = 0;

引用 19 楼 mujiok2003 的回复:
需要同步,因为有竞争,可以使用tsl或者以下方法

void foo()
{
  static std::string* pConfig = 0;
  if(!p)
  {
    lock theLock;
    if(!p)
    {
      static std::string config = "hello";
      pConfig = &config;
    }
  }
  //...
  //pConfig is correctly created
}

#21


会出问题,尚未初始化时,多线程竞速初始化,可能初始化多次。
解决办法:在子线程创建前,在主线程中调用一次以完成初始化。
你的加锁办法有损效率。

#22


引用 21 楼 proad 的回复:
会出问题,尚未初始化时,多线程竞速初始化,可能初始化多次。
解决办法:在子线程创建前,在主线程中调用一次以完成初始化。
你的加锁办法有损效率。

延迟初始化也有好处的. 如果从未被使用,则不需初始化.

#23


C++0X以后,要求编译器保证内部静态变量的线程安全性