[跟吉姆一起讀LevelDB]3.Memory Barrier與leveldb::DB::Open操作(2)
相關問題: C++的6種memory order
那memory barrier這個名詞是哪裡蹦出來的呢? Load是原子性操作, CPU不會Load流程走到一半, 就切換到另一個線程去了, 也就是Load本身是不會在多線程環境下產生問題的. 真正導致問題的是做這個操作的時機不確定!
1. 編譯器有可能讓指令亂序, 比如, int a=b; long c=b; 編譯器一旦判定a和c沒有依賴性, 就有權力讓這兩個取值操作以任意順序執行. 因為有可能有CPU指令可以一下取4個int, 亂序可以湊個整.
2. CPU會讓指令亂序, 原因同上, 但額外還有個原因是分支預測. AB線程都讀寫一個中間量c, B在處理c, 你預期B好了, A才會取. 但萬一A分支預測成功, B在處理的時候, A已經提前Load c進寄存器, 這就沒得玩了...
所以, 必須要有指令告訴CPU和編譯器, 不要改變這個變數的存取順序. 這就是Memory Barrier了. call MemoryBarrier保證前後一行是嚴格按照代碼順序的.
atomic_pointer.h 126-143行, 注意MemoryBarrier()的擺放,
class AtomicPointer {n private:n void* rep_;n public:n AtomicPointer() { }n explicit AtomicPointer(void* p) : rep_(p) {}n inline void* NoBarrier_Load() const { return rep_; }n inline void NoBarrier_Store(void* v) { rep_ = v; }n inline void* Acquire_Load() const {n void* result = rep_;n MemoryBarrier();n return result;n }n inline void Release_Store(void* v) {n MemoryBarrier();n rep_ = v;n }n}; n
大公司的開源項目真的是一個寶庫! 就算用不到, 各種踩了無數坑的庫, 編碼規則和跨平台代碼都是一般人沒機會完善的.
另外, 有菊苣在問題leveldb中atomic_pointer裡面memory barrier的幾點疑問?提到MemoryBarrier不保證CPU不亂序. 我覺得這個應該不用擔心. 因為MemoryBarrier的counterpart是std::atomic, 肯定嚴格保證語義相同啊. 實在不放心用std::atomic是墜吼的.
------
繼續上次沒讀完的Open部分代碼.
http://db_impl.cc 139-146行,
has_imm_.Release_Store(NULL); // atomic pointernn // Reserve ten files or so for other uses and give the rest to TableCache.n const int table_cache_size = options_.max_open_files - kNumNonTableCacheFiles;n table_cache_ = new TableCache(dbname_, &options_, table_cache_size);nn versions_ = new VersionSet(dbname_, &options_, table_cache_,n &internal_comparator_);n
- has_imm_, 用於判斷是否有等待或者正在寫入硬碟的immemtable
- table_cache_, SSTable查詢緩存
- versions_, 資料庫MVCC
has_imm_就是我上面描述的atomic pointer, 我推測這裡大概率Google程序員雇了一個臨時工(233), 把可以列表構造的has_imm_放到了函數部分, 因為這裡不存在任何race的可能性. db new完了. 說下一個很重要的原則, 構造函數究竟要做什麼? 阿里和Google共同的觀點: 輕且無副作用(基本就是賦值). 業務有需求的話, 兩步構造或者工廠函數, 二選一.
回到最早的工廠函數, 一個靠譜資料庫的Open操作, 用腳趾頭也能想到要從日誌恢複數據,
Status DB::Open(const Options& options, const std::string& dbname,n DB** dbptr) { // 工廠函數n *dbptr = NULL; // 設置結果默認值, 指針傳值nn DBImpl* impl = new DBImpl(options, dbname);n impl->mutex_.Lock(); // 數據恢復時上鎖, 禁止所有可能的後台任務n VersionEdit edit;n // Recover handles create_if_missing, error_if_existsn bool save_manifest = false;n Status s = impl->Recover(&edit, &save_manifest); // 讀log恢復狀態n if (s.ok() && impl->mem_ == NULL) {n // Create new log and a corresponding memtable. 複位n uint64_t new_log_number = impl->versions_->NewFileNumber();n WritableFile* lfile;n s = options.env->NewWritableFile(LogFileName(dbname, new_log_number),n &lfile);n if (s.ok()) {n edit.SetLogNumber(new_log_number);n impl->logfile_ = lfile;n impl->logfile_number_ = new_log_number;n impl->log_ = new log::Writer(lfile);n impl->mem_ = new MemTable(impl->internal_comparator_);n impl->mem_->Ref();n }n }n if (s.ok() && save_manifest) {n edit.SetPrevLogNumber(0); // No older logs needed after recovery.n edit.SetLogNumber(impl->logfile_number_);n s = impl->versions_->LogAndApply(&edit, &impl->mutex_); // 同步VersionEdit到MANIFEST文件n }n if (s.ok()) {n impl->DeleteObsoleteFiles(); // 清理無用文件n impl->MaybeScheduleCompaction(); // 有寫入就有可能要compactn }n impl->mutex_.Unlock(); // 初始化完畢n if (s.ok()) {n assert(impl->mem_ != NULL);n *dbptr = impl;n } else {n delete impl;n }n return s;n}n
------
就這樣, Open操作的脈絡大概應該是有了, 下篇再見.
推薦閱讀:
※阿里雲Elasticsearch的X-Pack:機器學習、安全保障和可視化
※[MySQL]獵聘網數據分析職位數據清洗
※如何用R訪問MySQL資料庫
※[跟吉姆一起讀LevelDB]5.資料庫恢復(2)