mutex一般用于为一段代码加锁,以保证这段代码的原子性(atomic)操作,即:要么不执行这段代码,要么将这段代码全部执行完毕。
例如,最简单的并发冲突问题就是一个变量自增1:
balance = balance + 1;
表面看这是一条语句,可是在背后的汇编中我们可以看到,指令集操作过程中会引入中间变量来保存右边的值,进而这个操作至少会被扩充为:
int tmp = balance + 1;
balance = tmp;
这就需要一把互斥锁(mutual exclusive, mutex)将这段代码给锁住,使其达到任何一个线程“要么全部执行上述代码,要么不执行这段代码”的效果。这个用法可以表示为:
lock_t mutex;
...
lock(&mutex)
balance = balance + 1;
unlock(&mutex);
那么,一个自然的问题便是,我如何实现上面的这个lock()
函数呢?
乍一看这个问题是非常复杂的,特别是考虑到它能够被适用于各种代码的各种情况。但经过各种简化,这个lock()
实现,可以通过几个test和set的组合得以实现。
例如,
typedef struct __lock_t { int flag; } lock_t;
void init(lock_t *mutex) {
// 0: lock is available
// 1: lock is held
mutex->flag = 0;
}
void lock(lock_t *mutex) {
while (mutex->flag == 1) { // Test the flag.
; // Wait the lock
mutex->flag = 1; // Set the lock, i.e. start to hold lock
}
void unlock(lock_t *mutex) {
mutex->flag = 0;
}
我第一次看到这个算法的时候非常惊讶,一个本来极其复杂的问题就这么优雅地被解决了。它仅仅涉及到对条件的检验和变量的复制,然后整个问题就这么轻而易举地被攻破了。
当然,我并没能看到上述代码的“坑”,也即是必须依靠指令集级别的支持才能真正做到atomic。这同样说明了并发程序的困难,稍微不注意便会调入一个万劫不复的坑里,并且你还不知道哪里出错了。
上述极端优雅的代码,有一个隐藏的坑,那便是在lock()
函数的实现里,while
循环那一段其实是可以被乱入的。
假设thread A是第一个运行到此的线程,那么它得到的mutex->flag
就肯定是0,于是它继续跳出循环往下运行,希望通过下面的mutex->flag = 1
来持有锁,使得其它线程在检测while
循环时为真,进而进入循环的等待状态。
可如果在A执行到这个赋值为1的语句之前,又有另外一个thread B运行到了这个while
循环部分,由于mutex->flag
还未被赋值为1,B同样可以跳出while
,从而跟A一样拿到这把锁!这就出现了冲突。
那怎么办呢?仔细后可以发现,其实关键问题就在于:
- 对
mutex->flag
的检测 - 对
mutex->flag
的赋值
这两个操作必须是不被干扰的,也就是它必须是atomic的,要么这两段代码不被执行,要么这两段代码被不中断地完整执行。
这就需要借助CPU指令集的帮助,来保证上述两条语句的atomic操作,也即是著名的TestAndSet()
操作。
int TestAndSet(int *ptr, int new) {
int old = *ptr;
*ptr = new;
return old;
}
CPU的指令集,并不需要支持繁复的各种atomic操作。仅仅支持上面这个函数,各种互斥加锁的情形,便都能够被涵盖。
此时,在回到我们最开始的那个优雅的lock()
实现,就可以将其改造为:
typedef struct __lock_t { int flag; } lock_t;
void init(lock_t *lock) {
// 0: lock is available
// 1: lock is held
mutex->flag = 0;
}
void lock(lock_t *mutex) {
while (TestAndSet(&lock_t->flag, 1) == 1) {
;
}
void unlock(lock_t *lock) {
lock->flag = 0;
}
上述代码极其精巧。乍一看在lock()
实现里不是还缺少一行mutex->flag = 1;
么?可其实呢,它已经被整合到了TestAndSet()
函数中。
这样的支持TestAndSet()
的实现,便是最简单的spin lock,弹簧锁。之所以叫弹簧锁,那是因为在各类锁当中,弹簧锁就是最初的被投入工业使用的最简单的实现技术。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。