ID生成器与本地静态变量 – 线程安全?

下面的代码段是否会在multithreading场景中按预期工作?

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

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

不,它不会。 您的处理器需要执行以下步骤才能执行此代码:

  • 从内存中获取ID到寄存器的值
  • 增加寄存器中的值
  • 将递增的值存储到内存中

如果在此(非primefaces)序列期间发生线程切换,则可能发生以下情况:

  • 线程a将值1提取到寄存器
  • 线程a递增值,因此寄存器现在包含2
  • 上下文切换
  • 线程b获取值1(仍在内存中)
  • 上下文切换
  • 将存储2线程化到内存并返回
  • 上下文切换
  • 线程b将其存储在寄存器中的值递增为2
  • 线程b(也)将值2存储到内存并返回2

所以,两个线程都返回2。

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

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

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

 int getUniqueIDBase() { static int ID=0; return ++ID; } int getUniqueID() { return getUniqueIDBase() << k + thread_id; } 

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

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

使用新的c ++ 0x你可以使用std::atomic类型。

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

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

注意:几乎使用了 这个词, 因为全局变量将在进程启动时初始化(即它的构造函数将在进入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而不赞成匿名命名空间,但我不赞成)。

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

您可以通过记录在函数内声明为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只是隐藏在函数内的全局对象。