Futex
在进入到brpc之前,先看看linux的futex是怎么用的。
man page中有比较详细的描述
可以把futex看作是一个原子的compare-and-block,他会原子的比较目标地址上的值,并进入睡眠。
通常的用法就是我们先通过一个用户空间的原子量来表示锁。当一个线程发现锁已经被获得了以后,他就会通过futex来睡眠,直到锁不再被其他人占有。
(感觉实现起来需要特殊处理signal,我们只要保证signal不出现在compare和block之间,就不会出现lost wakeup的问题)
futex主要的操作就两个,wake和wait
brpc已经帮我们封装好了。其中wait就是当addr1上的值为expected的时候,我们就睡眠。wake则是唤醒至多nwake个在addr1上睡眠的worker。
一般来说,nwake等于1的时候就是notify_one,nwake等于INT_MAX则是notify_all
addr1则是指向一个atmoic<int>
的指针。这个32位的值被称为futex word
brpc在OSX中实现了一个模拟的futex,我们可以看一下他是怎么实现的
然后是一个futex map,这里应该就是我们要等待的地址到futex的映射了。
而上面的SimuFutex则是相当于内核内部的工作了。
pthread_once的作用就是保证只会执行一次InitFutexMap。而那个init_futex_map_once是一个静态变量,可以看作是一个标志位。
我们首先通过要等待的addr找到对应的SimuFutex。增加引用计数。这里是为了让我们可以在没有人用这个futex的时候释放掉他
我们首先拿到这个futex上的锁,然后再执行一次原子load。如果不同的话就直接返回不需要等待了。否则的话进开始睡眠。增加一个count,表示在这个futex上等待的线程数量。然后根据是否有timeout来在cv上等待。
而wake就更简单了,找到对应的futex,然后在cv上唤醒nwake个thread
但是可以发现在futex wait中并不是一个循环的等待,也就是说当这个pthread被唤醒的时候,futex word上的值可能还是expected。
这个其实在manpage中也说的比较清楚:
This operation tests that the value at the futex word pointed to by the address uaddr still contains the expected value val, and if so, then sleeps waiting for a FUTEX_WAKE operation on the futex word.
可以看到和我上面的推测是类似的,我们需要保证compare和sleep的原子性,中间不能让其他的signal进入。这里的futex实现就是要获得内部的futex lock之后才能进行signal。
bthread中的parking lot就是通过futex来实现的。
整体代码相当少
parking lot的作用就是当bthread worker没有任务的时候,用来阻塞的地方。比如目前没有bthread,那么bthread worker就可以阻塞住,直到新的任务到来才会被唤醒。(有点类似cv,只不过我们可以控制唤醒的worker的数量,并且不需要锁)
可以看到之前的wait task中,如果我们发现当前的状态和上一次的状态相同的话,说明没有后续的任务进入,我们就可以睡眠等待。
当新的bthread进入的时候,task control会调用signal(1)表示一个新的bthread进入,我们就会唤醒一个worker来执行任务。
通过parking lot避免了worker的忙等待。本质就是一个计数器,通过记录任务的数量来控制bthread worker,从而防止他们不断在本地队列或者远端队列阻塞的情况
假设我们没有parking lot,那么worker就需要不断尝试从本地队列或者远端队列去pop任务,然而由于有两个队列,我们不能通过阻塞的队列来让他们一直等待
我之前实现的TinyThread就会不断的忙等待直到出现新的任务。
bthread通过任务数量而非任务队列来控制worker,非常巧妙的设计。
从futex的设计其实也可以看出来expected value是用来防止更新丢失的。因为他相当于是一个逻辑的锁,当我们在睡眠之前,会判断futex word和expected value是否相同,如果相同说明我们还拿着这个锁,我们就可以安心的睡眠。否则的话说明我们这个锁已经被别人拿走了,我们不应该去睡眠。即当我们拿到expected value的时候就相当于获得了这个逻辑锁,我们希望的是在我们睡眠之前,不要有人修改futex word。通过在真正睡眠的临界区进行判断,我们可以保证这个过程是串行的,即要么我们先睡眠,然后有人修改值把我们唤醒。要么有人先修改值,我们睡眠失败。
文章评论