WiredTiger hazard指針

在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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章