很多程序员经常考虑的一个问题是简单函数的线程安全性。比如下面的函数:
int g_i = 0;
void f() {
g_i++;
}
这个函数是否是线程安全的?换句话说,如果我在两个线程中调用它来更新同一个全局整形变量,会不会出什么问题?正如经常判断时所引用的标准所说,要看这个函数的操作是否具有原子性。
我们可以使用下面的命令将该函数编译成汇编语言来进行检验:
gcc -S code.c
vi code.s
1 movl _g_i, %eax
2 addl $1, %eax
3 movl %eax, _g_i
我们删去了一些code.s中不太关心的东西,仅仅留下了函数体所对应的逻辑。在第一行,将全局变量g_i的值读进寄存器eax,第二行将寄存器eax中的值加1,第三行将寄存器eax中的值写回到g_i中。注意,对全局变量g_i的操作实际是一个内存操作。这里对于多线程调用就会有一个问题。设想线程1和线程2同时将g_i的值读进eax,同时加1,然后依次写会g_i,,这样实际结果将是仅仅对g_i的值增加了1,而不是希望的2。可以清楚的看到,即便是这样简单的一个小程序也不是线程安全的。这也就是在Windows平台上提供了线程安全的Interlocked函数的原因。
大家如果有兴趣可以在自己的平台上编写一个简单的多线程程序尝试一下。但是要注意的是在某些平台上该函数的调用反映出来是线程安全的。这主要受到具体平台对于线程调度策略的影响。
但是出于兴趣的原因,我们也可以试图找到一种天然就是线程安全的简单函数写法。其实在IA32上,就我们的例子而言,有一个非常简单的方法。我们在产生可执行文件时,如果要求进行第二级的优化设置,就会发现可执行文件中对应于上面三行代码的新生成代码变成:
addl $1, _g_i
也就是会直接对存储器进行加法操作。这样即便不加锁保护,该代码依然是多线程安全的。大家不妨试一下。但是,这样的方法仅仅供消遣而已,在我们写真正代码时应该仍然让自己的代码是百分百线程安全的,而不应该依赖于编译器。