天天看點

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>

繼續閱讀