上篇笔记大致看了Java同步互斥机制在JVM层的实现. HotSpot在Linux平台是用pthread实现. 这篇继续对glibc(NPTL)在Linux平台上如何实现mutex和condition做个笔记.
glibc的源码极其难读, 因为要处理平台区别,性能优化,特殊情况处理等等, 源码的可读性不在大神们的考虑之列. 这里不是逐行源码解读, 列出来的代码都不是glibc的原版代码, 改动和简化了方便理解.
pthread mutex
pthread_mutex_t
|
|
pthread mutex可设置属性, 有如下类型(不同类型的mutex的lock/unlock实现不同):
- PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。
- PTHREAD_MUTEX_RECURSIVE_NP,可重入
- 其他的先略了…
pthread_mutex_lock
pthread_mutex_lock调用LLL_UNLOCK(基于Linux的futex), 去拿到锁或阻塞自己. 另外,对可重入的锁进行计数.
(不过, JDK/JVM层有自己的可重入锁设计, 并没有用到 PTHREAD_MUTEX_RECURSIVE_NP 的mutex.)
pthread_mutex_unlock
|
|
粗略来看, mutex主要是调用底层的lll_lock/lll_unlock, 其实就是调用futex的FUTEX_WAIT/FUTEX_WAKE操作, 来实现线程的休眠和唤醒工作.
low level lock
lll_lock/lll_unlock的lll应该是low level lock的缩写了. 这部分最为tricky, 不是因为汇编, 是并发的处理.
lll_lock, 先原子性的检查uaddr中计数器的值是否为val,如果是则让进程休眠,直到FUTEX_WAKE或者超时(time-out)。也就是把进程挂到uaddr相对应的等待队列上去。看代码:
do-while循环在这里的作用是: 从内核空间的wait里刚出来, 检查锁变量是不是就在这会儿被别人抢走, 如果以及被别人抢走, wait里出来继续后面的逻辑, 就要出错了.
这里还有个非常重要的点提一下: 当前线程(用户空间)看到锁变量被设置了, 准备进入内核, 打算去休眠自己, 如果在这个步骤1和步骤2的空隙里, 锁变量__lock又被清掉了, 这时候, 当前线程其实没有必要休眠了. 如果内核真的把当前线程休眠了,那就出问题了. 内核的futex的实现了还会再次判断__lock变量的值., 保证不出问题. 这个需要详细讨论futex了, 下次继续.
继续看lll_futex_wait, 就是汇编进行系统调用陷入内核.
lll_unlock也类似,只不过传的是FUTEX_WAKE参数.
终于看到陷入内核的代码了. (我的好奇心基本上被满足了…)
pthread condition variable
POSIX pthread标准里是pthread_cond_t/pthread_cond_wait()/pthread_cond_signal.
熟悉JDK的话, 发现这个逻辑和 J.U.C的AbstractQueuedSynchronizer.ConditionObject.await()很类似.
蓝色框里就是pthread_cond_wait的简化逻辑. 里面调用了FUTEX_WAIT,前面已经详述.
这里tricky的地方类似, 也是do-while循环, 从wait中醒来, 需要再次检查.
pthread_cond_signal,略…