假设有十个人需要分享一支笔(可能是因为他们工作的公司非常缺钱)。由于他们需要用笔写长篇文档,但撰写文档的大部分时间都是思考要写什么,所以他们决定每个人可以使用笔来写一句话,并随后将其提供给其他人。
现在我们有一个问题:如果两个人都想同时使用这支笔怎么办?我们可以说两个人都可以拿起笔,但这是一支脆弱的旧笔,所以如果两个人同时拿起笔,它就会断裂。相反,我们在笔周围画了一条粉笔线。首先你把手放在粉笔线上面,
然后你拿起笔。如果一个人的手在粉笔线里面,那么其他人就不允许把手放在粉笔线里面。如果两个人同时试图把手放在粉笔线上,根据这些规则只有一个人会首先进入粉笔线内部,因此另一个人必须收回手,并将其保持在粉笔线外,直到笔再次可用。
让我们将其与互斥锁相关联。互斥锁是一种保护共享资源(笔)的方式,保护的时间很短,被称为
关键区域(撰写文档一句话所需的时间)。每当你想使用资源时,你都要先调用
mutex_lock
(将手放在粉笔线内部)。每当你完成使用资源时,你都要调用
mutex_unlock
(将手从粉笔线区域中拿出来)。现在来讲一下互斥锁的实现方式。互斥锁通常是使用共享内存实现的。有一个名为“mutex”的共享不透明数据对象,
mutex_lock
和
mutex_unlock
函数都需要传入该对象的指针。
mutex_lock
函数使用原子的测试和设置或者链接装载/条件存储指令序列(在x86上通常使用
xhcg
)来检查和修改互斥锁内部的数据,然后要么“获取互斥锁”——将互斥锁对象的内容设置为指示临界区已锁定以告诉其他线程,要么就必须等待。最终,线程获得互斥锁,在关键部分执行工作并调用
mutex_unlock
。此函数将互斥锁内部的数据设置为标记为可用,并可能唤醒正在尝试获得互斥锁的休眠线程(这取决于互斥锁的实现方式——某些
mutex_lock
的实现方式只会在
xchg
上进行紧密循环,直到互斥锁可用,因此无需
mutex_unlock
通知任何人)。
为什么锁定互斥锁比访问内存更快?简而言之,缓存。CPU具有可以非常快地访问的缓存,因此只要处理器可以确保没有其他处理器访问该数据,
xchg
操作就不需要完全访问内存。但是x86有“拥有”缓存行的概念——如果处理器0拥有一个缓存行,则想要使用该缓存行中数据的任何其他处理器都必须通过处理器0。这样,
xhcg
操作就不需要查看缓存以外的任何数据,而且缓存访问往往非常快,因此获取未争用的互斥锁比内存访问更快。
不过,最后一段话有一个需要注意的地方:速度优势只适用于互斥锁未被争用的情况。如果两个线程同时尝试锁定同一把互斥锁,那么运行这些线程的处理器必须进行通信并处理相关的缓存行所有权,这大大降低了互斥锁获取的速度。此外,其中一个线程必须等待另一个线程执行关键部分的代码,然后释放互斥锁,从而进一步减慢其中一个线程获取互斥锁的速度。