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只是隐藏在函数内的全局对象。