在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);
}