互斥锁
介绍互斥锁的概念、用法,以及如何用互斥锁实现串行化器。
什么是互斥锁?
互斥锁(mutex)是一个支持两种操作的对象——互斥锁可以被获取(acquire),也可以被释放(release)。一旦某个互斥锁被获取,在该互斥锁被释放之前,任何其他获取该互斥锁的操作都不能进行。
互斥锁是一个可变对象(这里我们用一个单元素表,称之为单元格(cell)),可以保存值真或假。当值为假时,互斥锁可被获取。当值为真时,互斥锁不可用,任何试图获取该互斥锁的过程都必须等待。
我们的互斥锁构造函数 make-mutex 首先把单元格内容初始化为假。要获取互斥锁,我们测试单元格。如果互斥锁可用,我们把单元格内容设为真并继续。否则,我们在循环中等待,反复尝试获取,直到发现互斥锁可用。要释放互斥锁,我们把单元格内容设为假。
(define (make-mutex)
(let ((cell (list false)))
(define (the-mutex m)
(cond ((eq? m 'acquire)
(if (test-and-set! cell)
(the-mutex 'acquire))) ; 重试
((eq? m 'release) (clear! cell))))
the-mutex))
(define (clear! cell)
(set-car! cell false))
test-and-set! 测试单元格并返回测试结果。此外,如果测试结果为假,test-and-set! 会在返回假之前把单元格内容设为真。这个过程是 Scheme 原语,它的实现需要硬件支持。
使用互斥锁
教材在串行化器和原子硬件能力之间使用了一个中间抽象层,称为互斥锁。互斥锁和串行化器有什么区别?串行化器提供的是”受保护的操作”这一抽象,不要求程序员思考它受保护的机制。互斥锁则把事件序列暴露出来。正如前面那个错误实现中所写的
(set! in-use #t)
(apply proc args)
(set! in-use #f)
正确的版本使用类似的序列
(mutex 'acquire)
(apply proc args)
(mutex 'release)
警告
顺便说一下,本节中的所有版本都有另一个 bug;我们为了简化讨论忽略了返回值的问题。我们希望 protected-proc 返回的值与原 proc 返回的值相同,即使对 proc 的调用不是最后一步。因此正确的实现是
(mutex 'acquire)
(let ((result (apply proc args)))
(mutex 'release)
result)
正如教材第 311 页中的实现。
用互斥锁实现串行化器
现在我们已经了解了互斥锁及其用法,实现串行化器就相对直接了。我们的实现如下。务必弄清楚它如何工作、为什么能工作!
(define (make-serializer)
(let ((mutex (make-mutex)))
(lambda (p)
(define (serialized-p . args)
(mutex 'acquire)
(let ((val (apply p args)))
(mutex 'release)
val))
serialized-p)))