修改静态变量是否安全?
从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_init
, Py_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框架时,都会有一个很小的(但完全是浪费的)检查锁的开销。
结论
延迟初始化有其用途。 但是你最好不要尝试从多个线程执行延迟初始化。 而是有一个“安全线程”(主线程通常足够好),可以在创建任何尝试使用已初始化的线程之前执行延迟初始化。 那么你根本不用担心线程安全问题。