修改静态变量是否安全?

从C ++ 11开始,静态变量初始化保证是线程安全的。 但是如何修改多个线程中的静态变量呢? 如下

static int initialized = 0; Initialize() { if (initialized) return; initialized = 1; // Is this thread safe? } 

我问这个问题的原因是我正在阅读Py_Initialize()的源代码,我试图将Python嵌入到multithreadingC ++应用程序中,我想知道在多个线程中多次调用Py_Initialize()是否安全? Py_Initialize()的实现归结为函数_Py_InitializeEx_Private ,如下所示

 // pylifecycle.c static int initialized = 0; _Py_InitializeEx_Private(int install_sigs, int install_importlib) { if (initialized) return; initialized = 1; // a bunch of other stuff } 

C的结论与C ++相同吗?

编辑所以所有的答案都很好,我选择了最清楚我的头。

Py_Initialize不是线程安全的。 只有当你知道Python解释器已经被初始化时,你才可以从多个线程调用它,但是如果你能certificate调用函数是愚蠢的。

实际上,大多数Python C-API调用都不是线程安全的; 您需要获取全局解释器锁(GIL)才能与Python解释器进行交互。 (有关更多详细信息,请参阅Python C-API文档 。请仔细阅读。)

但是,据我所知,在初始化解释器之前,您无法使用标准API来获取GIL。 因此,如果您有多个线程,其中任何一个可能初始化相同的Python解释器,您需要使用自己的互斥锁保护对Py_Initialize的调用。 如果可以使用程序逻辑,那么在启动任何线程之前,最好先进行一次初始化。


您引用的代码:

 static int initialized = 0; void Initialize_If_Necessary() { if (initialized) return; initialized = 1; // Do the initialization only once } 

在任何语言中显然都不是线程安全的,即使initialized是primefaces类型。 假设两个线程在发生任何初始化之前同时执行此代码:它们都看到initialized为false,因此它们都继续初始化。 (如果你没有两个核心,你可以想象第一个进程是在initialized和赋值测试之间切换的任务。)

不,此上下文中的静态仅与存储持续时间有关(请参阅http://en.cppreference.com/w/c/language/static_storage_duration )。 该变量在某些其他变量上没有额外的线程安全性。

尝试使用std :: call_once,请参阅http://en.cppreference.com/w/cpp/thread/call_once

修改静态变量不是线程安全的,但初始化静态变量是线程安全的。 所以你可以这样做:

 void my_py_init() { static bool x = (Py_Initialize(), true); } 

而已。 您现在可以my_py_init从多个线程调用my_py_initPy_Initialize只会被调用一次。

跨多个线程修改静态变量是不安全的,因为如果将变量放入寄存器,那么同一寄存器中的其他核心信息将是不同的(修改另一个线程中的变量将与尝试访问该核心的变量相同)寄存器的版本,包含完全不同的数据)。

第一个代码示例是所谓的“延迟初始化”的典型起点。 它对于保证“昂贵物体”的一次性初始化非常有用; 但只有在任何使用对象之前需要时才这样做。

这个具体的例子没有任何严重的问题,但这是一个过于简单化的问题。 当你更全面地看待延迟初始化时,你会发现multithreading延迟初始化并不是一个好主意。


线程安全 ”的概念不仅仅是一个变量(静态或其他)。 您需要退后一步,同时考虑发生在相同的1个资源(内存,对象,文件……)中的事情

1:同一个类的不同实例不是一回事; 但他们的静态成员是。

请考虑第二个示例中的以下摘录。

 if (initialized) return; initialized = 1; // a bunch of other stuff 

在前3行中,如果多个线程大致同时执行该代码,则不会造成严重损害。 有些线程可能会提前返回; 其他人可能有点“太快”,并且都执行设置initialized = 1;的任务initialized = 1; 。 但是,这不是一个问题,因为无论有多少线程设置共享变量,净效果总是相同的。

问题出在第四行。 这个人几乎毫不在乎地说是“ 一堆其他东西 ”。 “ 其他东西 ”是真正关键的代码,因为如果initialized = 1;可能initialized = 1; 要被多次调用,你需要考虑多次同时调用“其他东西”的影响。


现在,在不太可能的情况下,你满足自己“其他东西”可以多次调用, 还有另外一个问题 ……

考虑可能使用Python的客户端代码。

 Py_Initialize(); //use Python 

如果2个线程同时调用上面的话; 1”早退’,另一个实际执行初始化。 然后’早期返回线程’将完全初始化之前使用Python启动(或尝试启动)

作为一个黑客攻击,您可能会尝试在初始化过程中阻塞if (initialized)行。 但这有两个原因:

  • 多个线程可能会在处理的早期阶段等待。
  • 即使在初始化完成后,每次“懒惰 – 初始化”Python框架时,都会有一个很小的(但完全是浪费的)检查锁的开销。

结论

延迟初始化有其用途。 但是你最好不要尝试从多个线程执行延迟初始化。 而是有一个“安全线程”(主线程通常足够好),可以在创建任何尝试使用已初始化的线程之前执行延迟初始化。 那么你根本不用担心线程安全问题。