天天看点

Redis源码学习——BIO

bio顾名思义,background io,是redis中运行的后台io。 网上千篇一律的说法是redis是单线程单进程。 实际上redis运行过程中并不是严格单进程单线程应用。

redis中的多进程:

在写入备份(rdb,aof)的时候,会fork出子进程进行备份文件的写入。

redis中的多线程:

aof的备份模式中,如果我们设置的是aof_fsync_everysec(每秒备份一次,这个设置可理解为弱同步备份),redis会create一个backgroud线程,在这个线程中执行aof备份文件的写入。

新生成的aof文件,在覆盖旧aof文件时。 如果在此之前aof备份已经开启,在执行该fd的close前,我们的redis进程与旧的aof文件存在引用, 旧的aof文件不会真正被删除。 所以当我们执行close(oldfd)时,旧aof文件的被打开该文件的进程数为0,即没有进程打开过这个文件,这时这个文件在执行close时会被真正删除。 而删除旧aof文件可能会阻塞服务,所以我们将它放到另一个线程调用。

redis将所有多线程操作封装到bio中,在bio.c,bio.h中可以看到。 本文我们关注的不是具体的操作,而是redis封装的bio行为, 这个代码简洁,维护性好。 值得学习一下。

bio提供以下几个api:

bio操作的类型:

bio对象:

我们先看初始化的时候执行的部分:

主要功能分为两个部分:

biocreatebackgroundjob: 创建bio任务,插入bio_jobs,并调用pthread_cond_signal,通知进程解锁。

bioprocessbackgroundjobs: 执行bio任务线程。 线程中通过pthread管理进程锁,当biocreatebackgroundjob执行pthread_cond_signal通知到该任务对应的线程时,从bio_jobs读出上一个任务,并执行。

整个bio就是通过锁进行的阻塞后台io。 如果我们梳理一下这个锁过程:

bioinit,新建线程,执行bioprocessbackgroundjobs。

bioprocessbackgroundjobs 中,pthread_mutex_lock(&bio_mutex[type]),给该任务的锁变量加锁。

进入while循环, 调用pthread_cond_wait, 等待解锁。 由于mutex锁是“sleep-lock”,线程会sleep,等待唤醒。

主线程调用创建bio任务, 调用biocreatebackgroundjob。

biocreatebackgroundjob中 pthread_mutex_lock(&bio_mutex[type]); 又对bio_mutex[type]加锁

biocreatebackgroundjob中pthread_cond_signal(&bio_newjob_cond[type]) //发送信号,通知bio线程继续执行。

biocreatebackgroundjob中pthread_mutex_unlock(&bio_mutex[type]); //解锁

bioprocessbackgroundjobs 中被唤醒继续进行。

执行任务完毕后,pthread_mutex_unlock解锁, pthread_cond_broadcast广播解锁。

再pthread_mutex_lock加锁 。 用于下一次while循环。

在梳理的时候,我发现一个奇怪的地方,我们第2步在bio线程中加锁,第5步调用biocreatebackgroundjob在主线程中又对mutex进行了一次加锁。 而在他们之间并没有pthread_mutex_unlock执行。 为什么biocreatebackgroundjob没有被mutex的锁阻塞?

一切的关键都在pthread_cond_wait这个函数中。 按照我原来的理解,pthread_cond_wait应该只是进行了一次信号等待, 等到某个信号后,将mutex[type]解锁。 为什么在信号发送前,pthread_mutex_lock没有将主线程的biocreatebackgroundjob阻塞住。 所以我猜测, pthread_cond_wait不不仅仅是一次wait signal,而是unlock+wait。

为了验证这个猜想,我们进去看pthread_cond_wait的实现:

<a href="https://github.com/lattera/glibc/blob/master/nptl/pthread_cond_wait.c#l127">glibc中的pthread_cond_wait</a>

可以看到, pthread_cond_wait 实际上就是一次 unlock -&gt; wait -&gt; lock。

<a href="https://github.com/antirez/redis">redis源码</a>

<a href="https://github.com/huangz1990/redis-3.0-annotated">redis源码注释</a>

继续阅读