排他锁和锁中间状态

2015年11月22日

定义

在计算机世界中,当在同一个时间点,有大于1个进程需要使用到同一个资源,这个时候就需要锁了。这里有两个关键词:一个资源,多个进程。

兼容锁

兼容锁的意思是,虽然资源被锁上了,但是还可以被使用。

排他锁

排他锁的意思是,只要资源被锁上,就只能被一个进程使用。

正文

因为工作中的一些经历,这篇文章作者主要想讲一讲排它锁的锁中状态。

在工作中,对于锁的使用常常都是排他的。比如,用一个文件去记录线上的当前版本,当更新这个文件的时候,写入时并发请求会读到空的情况;又比如,如果用git去搞一个发布工具,当一个人在合并分支的时候,另外人也请求合并分支,会发生什么情况?这个时候,一般人想到的就是用一个排他锁去锁住它。

一旦用到锁,有个问题是必须回答的:锁的中间状态,如何处理其它调用?

一般有下面几种方式:

  1. 其它调用阻塞,等待锁被释放。这种方式可能造成死锁,把进城堵塞死。
  2. 其它调用直接返回错误。比如上面多人同时操作git的情况。

但是,问题来了。上面说到的第一个例子,网站的请求,总不能在版本发布的时候直接访问错误吧?

mysql的事务处理是这样子的:

  1. 对于被锁住的数据,提供一个旧的版本,加上个写的互斥锁
  2. 如果是一个读的调用,直接读取到旧版本快照
  3. 如果是一个写的调用,则阻塞主

注:这里其实也牵扯到可用性和一致性的取舍,但这不是本文的主题

按照这种思路,我们来解决下下面几个案例:

例子一、用文件管理线上版本

可能遇到的情况

版本上线,更新这个文件的时候,并发请求会读到空的情况

解决思路

  1. 是不是点了更新按钮用户一定要马上看到最新页面?【显然不是】
  2. 那么我们可以也搞一个旧版本的快照,这种情况就是另外一个文件。每次更新的时候,先更新 VERSION,更新完成后再去更新 VERSION.old。每次读取版本,如果从 VERSION 中取到为空,再去 VERSION.old 取。

例子二、使用git管理php线上版本

注:这里php的部署方式是fpm的。由于php的加载模式,所以会碰到git版本变更的中间状态

  1. 是不是点了更新按钮用户一定要马上看到最新页面?【显然不是】
  2. 我们同样可以搞一个旧版本的快照。这应该是另外一个文件夹,还需要一个管理两个文件夹切换的方案,可以参考使用文件管理版本的逻辑。

例子三、使用git的工具合并版本

  1. 这是一个写入操作,也是一个操作量很小的工具
  2. 一个人在使用这个文件夹,其他人直接返回不能使用就行了。

总结

  1. 先确认并发的操作是写还是读
  2. 如果是写,一般而言可以采用返回错误或者阻塞的方式
  3. 如果是读,看它是否对一致性要求很高,如果不高的话,还可以使用一个旧的快照来增加可用性