在WiredTiger裏面, 採用Hazard pointers來管理一個內存頁是否可以被Evict, 本文分析下其實現過程。
Hazard pointers
Hazard pointers是在多線程環境下實現資源無鎖訪問的一種方法, 它採用空間換時間的方法:
- 爲每一個資源分配一個Hazard指針數組,數組大小等於線程個數, 每一項裏面包含一個指針指向某個資源或者爲空;
- 每當線程要訪問資源的時候, 將該線程對應的Hazard pointer修改: 資源的指針賦給Hazard指針包含的資源指針;
- 當一個線程完成訪問, 將該線程的Hazard指針設定爲空;
- 當要刪除資源, 遍歷Hazard 指針數組, 看是否有其他線程的Hazard指針還在指向該資源, 如果沒有就刪除, 否則就不能刪除;
WIREDTIGER中的使用
在WIREDTIGER裏面, 使用Hazard pointers來判斷某個內存頁能不能從內存刷到磁盤上面。
struct __wt_hazard {
WT_PAGE *page; /* 內存頁對象 */
#ifdef HAVE_DIAGNOSTIC
const char *file; /* File/line where hazard acquired */
int line;
#endif
};
struct WT_COMPILER_TYPE_ALIGN(WT_CACHE_LINE_ALIGNMENT) __wt_session_impl {
/*
* Hazard pointers.
*
* session的hazard爲空, 代表第一次使用
*/
#define WT_SESSION_FIRST_USE(s) \
((s)->hazard == NULL)
uint32_t hazard_size; /* 分配的hazard數組大小. */
uint32_t nhazard; /* 已使用的hazard指針數目 */
WT_HAZARD *hazard; /* Hazard pointer 數組 */
};
這裏, 可以看到在每個session裏面, 定義了一個WT_HAZARD數組, 大小是hazard_size, hazard_size在創建的時候, 檢查session_count來賦值, 就是說保證每一個線程, 都在數組裏面有唯一的一個index, 每當某個線程要操作內存頁, 就會將該內存頁的指針賦予該線程對應的Hazard指針, 由於某個線程只能也只需要修改自己線程index對應的Hazard指針, 不會修改到別的線程的Hazard pointers, 而且任何線程都可以讀到所有的其他線程Hazard指針的使用情況, 因此對於session->hazard數組的修改是安全的, 不需要加鎖。
當我們要決定是否可以將某個內存頁轉存到磁盤, 可以遍歷session->hazard數組, 如果有某個線程還在使用該內存頁就無法刷盤。
/*
* __wt_page_hazard_check --
* Return if there's a hazard pointer to the page in the system.
*/
static inline WT_HAZARD *
__wt_page_hazard_check(WT_SESSION_IMPL *session, WT_PAGE *page)
{
WT_CONNECTION_IMPL *conn;
WT_HAZARD *hp;
WT_SESSION_IMPL *s;
uint32_t i, j, hazard_size, max, session_cnt;
conn = S2C(session);
/*
* No lock is required because the session array is fixed size, but it
* may contain inactive entries. We must review any active session
* that might contain a hazard pointer, so insert a barrier before
* reading the active session count. That way, no matter what sessions
* come or go, we'll check the slots for all of the sessions that could
* have been active when we started our check.
*/
WT_STAT_FAST_CONN_INCR(session, cache_hazard_checks);
WT_ORDERED_READ(session_cnt, conn->session_cnt);
for (s = conn->sessions, i = 0, j = 0, max = 0;
i < session_cnt; ++s, ++i) {
if (!s->active)
continue;
WT_ORDERED_READ(hazard_size, s->hazard_size);
if (s->hazard_size > max) {
max = s->hazard_size;
WT_STAT_FAST_CONN_SET(session,
cache_hazard_max, max);
}
for (hp = s->hazard; hp < s->hazard + hazard_size; ++hp) {
++j;
if (hp->page == page) {
WT_STAT_FAST_CONN_INCRV(session,
cache_hazard_walks, j);
return (hp);
}
}
}
WT_STAT_FAST_CONN_INCRV(session, cache_hazard_walks, j);
return (NULL);
}