在《多线程编程之数据访问互斥》一文中简单介绍了原子锁,这里再详细说一下原子锁的概念和用途。
(1)简单数据操作
如果在一个多线程环境下对某个变量进行简单数学运算或者逻辑运算,那么就应该使用原子锁操作。因为,使用临界区、互斥量等线程互斥方式将涉及到很多操作系统调用和函数调用等,效率肯定不如原子操作高。比如有这样一个例子:
unsigned int count = 0; int data_process() { if(/* conditions */){ EnterCriticalSection(&cs); ++ count; LeaveCriticalSection(&cs); } }
这里只有简单的数学操作,完全可以应用操作系统提供的原子操作来替代,效率会高不少:
unsigned int count = 0; int data_process() { if(/* conditions */){ InterLockedIncrement (&count); } }
(2)代码段中的互斥
还是以临界区为例,比如在代码段中应用到了如下的临界区:
void data_process() { EnterCriticalSection(&cs); do_something(); LeaveCriticalSection(&cs); }
这里其实也可以利用原子锁来代替临界区,实现方式如下:
unsigned int lock = 0; void data_process() { while(1 == InterLockedCompareExchange(&lock, 1, 0)); do_something(); lock = 0; }
InterLockedCompareExchange方法的含义是:将第一个参数的值与第三个参数的值进行比较,如果相等则与第二个参数的值进行交换,如果不相等则不进行操作;返回值为第一个参数的初始值,该函数所进行的操作为原子操作,不会被多线程中断,可适用于所有CPU。
上面的代码含义就是不断监测lock的值,当lock值为0时,InterLockedCompareExchange就会将其变为1,并执行do_something(),最后将lock还原为0。当然,如果lock此时为1,这个while循环就会不断地执行(“忙等”)。
其实说到这里,我们就会发现这里有一个问题:如果另外有一个线程将lock变为1并且需要执行很长的时间,那么这里岂不成了疯等?没错,所以说这种原子锁方法适用于短时间操作线程的互斥场合,并不能替代所有系统互斥锁调用场合。临界区在此时就会表现得更有优越性。