C++:多线程程序中的静态变量

37
在多线程程序中,使用静态变量(特别是在函数内部)可能存在问题。谢谢。
3个回答

31

初始化不具备线程安全性。两个线程可以进入该函数,并且两者都可能初始化函数作用域的静态变量。这不好。无法确定结果可能会是什么。

C++0x中,函数作用域静态变量的初始化将是线程安全的。调用该函数的第一个线程将初始化变量,任何其他调用该函数的线程将需要阻塞,直到该初始化完成。

我认为目前没有编译器+标准库对实现C++0x并发内存模型、线程支持和原子库提供完整支持。


编译器在第一次调用时初始化静态变量的可能性真的很大吗?从我所见过的最常用的编译器来看,它们只是像全局变量一样处理。我刚刚确认了这一点,通过查看使用“static int”的一些代码的汇编输出,证实了GCC也是如此处理的。 - asveikau
1
@asveikau:当然,任何具有副作用的动态初始化都必须在函数第一次调用时完成,而不是更早。在您的示例中,如果使用常量表达式初始化 static int,则没有动态初始化,并且对象会在函数第一次进入之前被初始化。(我不打算在这里写一个关于静态初始化的许多复杂性的专题;有许多细节。我只想在这里涵盖最棘手的情况。) - James McNellis
一些问题在于我一直在用C语言思考,据我所知,在C语言中你不能使用非常量表达式初始化静态变量。但我认为在C++中是可以这样做的。 - asveikau
@asveikau:在C++中可以这样做。至于C,我对C的熟悉程度远不及C++。 - James McNellis

25

举个随机的例子,考虑像C库中的asctime这样的接口。原型看起来像这样:

 char *
 asctime(const struct tm *timeptr);

这意味着隐式地必须有一些全局缓冲区来存储返回的char*中的字符。实现这一点最常见且简单的方法是:

 char *
 asctime(const struct tm *timeptr)
 {
    static char buf[MAX_SIZE];

    /* TODO: convert timeptr into string */

    return buf;
 }

在多线程环境中,这种方法完全失效,因为每次调用asctime()时,buf的地址都相同。如果两个线程同时调用asctime(),它们有可能会互相覆盖结果。而asctime()的契约中隐含着字符串的字符将会一直保留到下一次asctime()的调用,而并发调用则会破坏这个契约。

有一些语言扩展可以通过线程本地存储(__thread,__declspec(thread))来解决这个问题。我认为这个想法成为了C++0x的关键字thread_local

即使如此,我认为像这样使用它是一个糟糕的设计决策,理由与使用全局变量的情况类似。此外,可能会将其视为更干净的接口,由调用者维护和提供这种状态,而不是调用方。然而,这些都是主观的论点。


2

静态变量通常意味着函数的多次调用会共享状态,从而相互干扰。

通常情况下,您希望函数是自包含的;拥有本地副本并与外界不共享任何东西,除了参数和返回值。(如果您以某种方式思考,这些也不属于函数的一部分。)

请考虑以下内容:

int add(int x, int y);

绝对是线程安全的,x和y是本地副本。

void print(const char *text, Printer *printer);

危险,有人可能在外部使用同一个打印机,例如对其调用另一个print()函数。

void print(const char *text);

绝对不是线程安全的,两个并行调用肯定会使用同一个打印机。

当然,有方法可以保护共享资源的访问(搜索关键字:mutex);这只是你的直觉应该如何。

未同步的并行写入变量大多数情况下也不是线程安全的,读和写也是一样的。(搜索关键字:synchronizationsynchronization primitives(其中mutex只是其中之一),还有atomicity/atomic operation,用于在并行访问时保证安全。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接