初始化不具备线程安全性。两个线程可以进入该函数,并且两者都可能初始化函数作用域的静态变量。这不好。无法确定结果可能会是什么。
C++0x中,函数作用域静态变量的初始化将是线程安全的。调用该函数的第一个线程将初始化变量,任何其他调用该函数的线程将需要阻塞,直到该初始化完成。
我认为目前没有编译器+标准库对实现C++0x并发内存模型、线程支持和原子库提供完整支持。
举个随机的例子,考虑像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
。
即使如此,我认为像这样使用它是一个糟糕的设计决策,理由与使用全局变量的情况类似。此外,可能会将其视为更干净的接口,由调用者维护和提供这种状态,而不是调用方。然而,这些都是主观的论点。
静态变量通常意味着函数的多次调用会共享状态,从而相互干扰。
通常情况下,您希望函数是自包含的;拥有本地副本并与外界不共享任何东西,除了参数和返回值。(如果您以某种方式思考,这些也不属于函数的一部分。)
请考虑以下内容:
int add(int x, int y);
绝对是线程安全的,x和y是本地副本。
void print(const char *text, Printer *printer);
危险,有人可能在外部使用同一个打印机,例如对其调用另一个print()函数。
void print(const char *text);
绝对不是线程安全的,两个并行调用肯定会使用同一个打印机。
当然,有方法可以保护共享资源的访问(搜索关键字:mutex
);这只是你的直觉应该如何。
未同步的并行写入变量大多数情况下也不是线程安全的,读和写也是一样的。(搜索关键字:synchronization
、synchronization primitives
(其中mutex
只是其中之一),还有atomicity
/atomic operation
,用于在并行访问时保证安全。)
static int
,则没有动态初始化,并且对象会在函数第一次进入之前被初始化。(我不打算在这里写一个关于静态初始化的许多复杂性的专题;有许多细节。我只想在这里涵盖最棘手的情况。) - James McNellis