具有本地静态变量的ID生成器 - 线程安全?

时间:2010-04-24 18:35:25

标签: c++ c thread-safety

以下一段代码是否会在多线程场景中按预期工作?

int getUniqueID()  
{  
    static int ID=0;  
    return ++ID;  
}

ID不必连续 - 即使它跳过一个值,也没关系。 可以说当这个函数返回时,返回的值在所有线程中都是唯一的吗?

6 个答案:

答案 0 :(得分:18)

答案 1 :(得分:5)

不,比赛仍有潜力,因为增量不一定是原子的。如果你使用原子操作来增加ID,这应该可行。

答案 2 :(得分:4)

++不一定是原子的,所以不,这不是线程安全的。但是,很多C运行时提供了原子版本,例如,对于gcc __sync_add_and_fetch()和Windows上的InterlockedIncrement()

答案 3 :(得分:3)

如果您只需要在N个线程中单调增加(或非常接近)数字,请考虑这个(k是某个数字,使得2 ^ k> N):

int getUniqueIDBase()  
{  
    static int ID=0;  
    return ++ID;  
}

int getUniqueID()
{
    return getUniqueIDBase() << k + thread_id;
}

答案 4 :(得分:2)

getUniqueID至少有两种竞争条件。在初始化ID和增加ID时。我已经重写了函数以更清楚地显示数据竞争。

int getUniqueID()  
{
   static bool initialized = false;
   static int ID;
   if( !initialized )
   {
      sleep(1);
      initialized = true;

      sleep(1);
      ID = 1;      
   }

   sleep(1);
   int tmp = ID;

   sleep(1);
   tmp += 1;

   sleep(1);
   ID = tmp;

   sleep(1);
   return tmp;
}

增量具有欺骗性,它看起来很小,以至于假设它是原子的。但它是一个加载 - 修改 - 存储操作。将值从内存加载到CPU寄存器。 inc登记册。将寄存器存回内存。

使用新的c ++ 0x,您只需使用std::atomic类型。

int getUniqueID()  
{  
    static std::atomic<int> ID{0};  
    return ++ID;  
}

注意:技术上我说谎了。零初始化全局变量(包括函数静态)可以存储在bss内存中,一旦程序启动就不需要初始化。但是,增量仍然是一个问题。

答案 5 :(得分:0)

注意:使用几乎这个词,因为全局变量将在进程启动时初始化(即在进入main之前将调用其构造函数)而内部的静态变量函数将在第一次执行语句时初始化。

你的问题从一开始就是错误的:

  

具有本地静态变量的ID生成器 - 线程安全吗?

在C / C ++中,函数内部或类/结构声明内部的静态变量(几乎)表现为全局变量,而不是基于本地堆栈的变量。

以下代码:

int getUniqueID()  
{  
    static int ID=0;  
    return ++ID;  
}

将(几乎)与伪代码相似:

private_to_the_next_function int ID = 0 ;

int getUniqueID()  
{  
    return ++ID;  
}

使用伪关键字private_to_the_next_function使变量对所有其他函数不可见但getUniqueId ...

在这里,static只隐藏变量,使其无法从其他函数访问...

但即使隐藏,变量ID仍然是全局的:多个线程应该调用getUniqueId, ID将与其他全局变量一样是线程安全的,也就是说,根本不是线程安全的

编辑:变量的生命周期

在看完评论后,我觉得我的答案不够清楚。我没有使用全球/本地概念来表示其范围,但是对于它们的终身意义:

只要进程正在运行,全局就会生效,并且在堆栈上分配的本地将在进入范围/函数时开始其生命,并且在范围/函数退出时将停止存在。这意味着全球将保留其价值,而本地则不会。这也意味着线程之间将共享全局,而本地则不会。

添加static关键字,它具有不同的含义,具体取决于上下文(这就是为什么在全局变量和C ++中的函数上使用static而不赞成使用匿名命名空间,但是我disgress)。

在限定局部变量时,此局部不再像本地一样。它成为一个隐藏在函数内部的全局。所以它的行为好像在函数调用之间神奇地记住了局部变量的值,但是没有魔法:变量是全局变量,并且在程序结束之前将保持“活着”。

您可以通过记录在函数内声明为static的对象的创建和销毁来“看到”这一点。构造将在执行声明语句时发生,并且破坏将在过程结束时发生:

bool isObjectToBeConstructed = false ;
int iteration = 0 ;

struct MyObject
{
   MyObject() { std::cout << "*** MyObject::MyObject() ***" << std::endl ; }
   ~MyObject() { std::cout << "*** MyObject::~MyObject() ***" << std::endl ; }
};

void myFunction()
{
   std::cout << "   myFunction() : begin with iteration " << iteration << std::endl ;

   if(iteration < 3)
   {
      ++iteration ;
      myFunction() ;
      --iteration ;
   }
   else if(isObjectToBeConstructed)
   {
      static MyObject myObject ;
   }

   std::cout << "   myFunction() : end with iteration " << iteration << std::endl ;
}


int main(int argc, char* argv[])
{
   if(argc > 1)
   {
      std::cout << "main() : begin WITH static object construction." << std::endl ;
      isObjectToBeConstructed = true ;
   }
   else
   {
      std::cout << "main() : begin WITHOUT static object construction." << std::endl ;
      isObjectToBeConstructed = false ;
   }

   myFunction() ;

   std::cout << "main() : end." << std::endl ;
   return 0 ;
}

如果你启动没有参数的可执行文件,执行将永远通过静态对象声明,因此,它永远不会被构造也不会被破坏,如日志所示:

main() : begin WITHOUT static object construction.
   myFunction() : begin with iteration 0
   myFunction() : begin with iteration 1
   myFunction() : begin with iteration 2
   myFunction() : begin with iteration 3
   myFunction() : end with iteration 3
   myFunction() : end with iteration 2
   myFunction() : end with iteration 1
   myFunction() : end with iteration 0
main() : end.

但是如果使用参数启动它,那么该对象将在myFunction的第三次递归调用中构造,并且仅在进程结束时销毁,如日志所示:

main() : begin WITH static object construction.
   myFunction() : begin with iteration 0
   myFunction() : begin with iteration 1
   myFunction() : begin with iteration 2
   myFunction() : begin with iteration 3
*** MyObject::MyObject() ***
   myFunction() : end with iteration 3
   myFunction() : end with iteration 2
   myFunction() : end with iteration 1
   myFunction() : end with iteration 0
main() : end.
*** MyObject::~MyObject() ***

现在,如果你使用相同的代码,但通过多个线程调用myFunction,你将在myObject的构造函数上有竞争条件。如果你调用这个myObject方法或者在多个线程调用的myFunction中使用这个myObject变量,你也会有竞争条件。

因此,静态局部变量myObject只是隐藏在函数内的全局对象。