记得大学刚毕业那年看了侯俊杰的《深入浅出MFC》,就对深入浅出这四个字特别偏好,并且成为了自己对技术的要求标准——对于技术的理解要足够的深刻以至于可以用很浅显的道理给别人讲明白。以下内容为个人见解,如有雷同,纯属巧合,如有错误,烦请指正。

因为leveldb很多类型的声明和实现分别在.h和.cc两个文件中,为了代码注释方便,我将二者合一(类似JAVA和GO类的定义方法),读者在源码中找不到我引用的部分属于正常现象。


目录

Slice

SequenceNumber

Varint

ValueType

InternalKey

LookupKey

Comparator

BytewiseComparatorImpl

InternalKeyComparator

leveldb_comparator_t

Iterator

AtomicPointer


Slice

Slice作为leveldb最基础的类型之一,应用非常广泛,关键点在于leveldb的key和value两个类型都是Slice类型。其实Slice类型和std::string(std::string也是可以用来存储binary数据,protobuf就是用std::string实现的binary数据存储)非常类似,但是要比std::string轻量很多。不用多说,都在注释里:

// 代码源自leveldb/include/leveldb/slice.h
class Slice {
public:// 没有任何参数的构造函数,默认数据指向一个空字符串,而不是NULLSlice() : data_(""), size_(0) { }// 通过字符串指针和长度构造Slice,此方法可以用于截取部分字符串,也可以用于binary类型数据// 我好奇的是为什么没有用const void*作为d的参数类型,这样会更通用一点Slice(const char* d, size_t n) : data_(d), size_(n) { }// 通过std::string构造Slice,因为没有用explicit声明,说明可以Slice s = std::string()方式赋值Slice(const std::string& s) : data_(s.data()), size_(s.size()) { }// 通过字符串指针构造Slice,大小就是字符串长度Slice(const char* s) : data_(s), size_(strlen(s)) { }// 返回数据指针,返回类型是const char*而不是void*需要注意一下const char* data() const { return data_; }// 返回数据长度size_t size() const { return size_; }// 判断Slice是否为空,仅通过size_等于0,因为默认构造函数data="",不是NULL,所以不能用空指针判断bool empty() const { return size_ == 0; }// 重载了[]运算符,那么就可以像数组一样访问Slice了,返回的类型是char型char operator[](size_t n) const {assert(n < size());return data_[n];}// 清空Slicevoid clear() { data_ = ""; size_ = 0; }// 删除前面一些数据void remove_prefix(size_t n) {assert(n <= size());// 对于Slice来说就是指针偏移data_ += n;size_ -= n;}// 把Slice转换为std::stringstd::string ToString() const { return std::string(data_, size_); }// 比较函数,基本上和std::string是相同的比较方法int compare(const Slice& b) const {// 取二者最小的长度,避免越界const size_t min_len = (size_ < b.size_) ? size_ : b.size_;// 直接使用memcmp()函数实现int r = memcmp(data_, b.data_, min_len);// 二者相等还有其他情况,那就是连个Slice的长度不同时,谁的更长谁就更大if (r == 0) {if (size_ < b.size_) r = -1;else if (size_ > b.size_) r = +1;}return r;     }// 判断Slice是不是以某个Slice开头的,这个普遍是拿Slice作为key使用的情况,因为leveldb的key是字符串型的,而且经常以各种前缀做分类bool starts_with(const Slice& x) const {return ((size_ >= x.size_) && (memcmp(data_, x.data_, x.size_) == 0));}private:// 就两个成员变量,一个指向数据,一个记录大小const char* data_;size_t size_;
};
// 同时还重载了连个全局的运算符==和!=,应该还是比较简单的哈
inline bool operator==(const Slice& x, const Slice& y) {return ((x.size() == y.size()) && (memcmp(x.data(), y.data(), x.size()) == 0));
}
inline bool operator!=(const Slice& x, const Slice& y) {return !(x == y);
}

Slice类型不像std::string有内存管理能力,所有的内存由Slice外部管理,这一点需要注意一下。

SequenceNumber

SequenceNumber是一个无符号64位整型的值,我们这里用“顺序号”这个名字。leveldb每添加/修改一次记录都会触发顺序号的+1。

// 代码源自leveldb/db/dbformat.h
typedef uint64_t SequenceNumber;

Varint

Varint是一种比较特殊的整数类型,它包含有Varint32和Varint64两种,它相比于int32和int64最大的特点是长度可变。我们都知道sizeof(int32)=4,sizeof(int64)=8,但是我们使用的整型数据真的都需要这么长的位数么?举个例子,我们的使用leveldb存储的键,很可能长度连一个字节都用不上,但是leveldb又不能确定用户键的大小范围,所以Varint就应运而生了。

因为Varint没法用具体的结构体或者标准类型表达,所以使用的时候需要编码/解码(亦或是序列化/反序列化)过程,我们通过代码就可以清晰的了解Varint的格式了。

// 代码源自leveldb/util/coding.cc
// 我们指的Varint32就是存储在dst中,按照Varint32格式封装的数据
char* EncodeVarint32(char* dst, uint32_t v) {unsigned char* ptr = reinterpret_cast<unsigned char*>(dst);static const int B = 128;// 小于128,存储空间为一个字节,[v]if (v < (1<<7)) {*(ptr++) = v;}// 大于等于128小于16K用两个字节存储[v(7-13位), v(0-6位)|128] else if (v < (1<<14)) {*(ptr++) = v | B;*(ptr++) = v>>7;}// 大于等于16K小于2M用3个字节存储[v(14-20位),v(7-13位)|128, v(0-6位)|128] else if (v < (1<<21)) {*(ptr++) = v | B;*(ptr++) = (v>>7) | B;*(ptr++) = v>>14;}// 大于等于2M小于256M用4个字节存储[v(21-27位),v(14-20位)|128,v(7-13位)|128, v(0-6位)|128] else if (v < (1<<28)) {*(ptr++) = v | B;*(ptr++) = (v>>7) | B;*(ptr++) = (v>>14) | B;*(ptr++) = v>>21;}// 大于等于256M用5个字节存储[v(28-32位),v(21-27位)|128,v(14-20位)|128,v(7-13位)|128, v(0-6位)|128] else {*(ptr++) = v | B;*(ptr++) = (v>>7) | B;*(ptr++) = (v>>14) | B;*(ptr++) = (v>>21) | B;*(ptr++) = v>>28;}return reinterpret_cast<char*>(ptr);
}

上面是int32编码成Varint32的源代码,其实他的编码风格有点类似utf-8,字节从低到高存储的是整型的从低到高指定位数,每个字节的最高位为标志位,为1代表后面(更高位)还有数,直到遇到一个字节最高位为0。所以每个字节的有效位数只有7位,这样做虽然看似浪费了一些空间,如果我们使用的整型数据主要集中在2M以内的话,那么我们反而节省了2个字节的空间。

解码以及Varint64相关的代码读者自行看吧,原理已经了解了,这些已经没有什么技术含量了。

ValueType

ValueType我们直译成“值类型”,在leveldb中,值的类型只有两种,一种是有效数据,一种是删除数据。因为值类型主要和对象键配合使用,这样就可以知道该对象是有值的还是被删除的。在leveldb中更新和删除都不会直接修改数据,而是新增一条记录,后期合并会删除老旧数据。

// 代码源自leveldb/db/dbformat.h
enum ValueType {kTypeDeletion = 0x0,                               // 删除kTypeValue = 0x1                                   // 数据
};
static const ValueType kValueTypeForSeek = kTypeValue; // 用于查找

在查找对象时,对象不能是被删除的,所以kValueTypeForSeek等于kTypeValue。

InternalKey

类型名字已经非常能代表意思了,虽然用户在使用leveldb的时候用Slice作为key,但是在leveldb内部是以InternalKey作为Key的。所以我们非常有必要了解一下这个类型到底和Slice有什么不同,而且为什么要这么实现。

// 代码源自leveldb/db/dbformat.h
class InternalKey {
private:// 只有一个私有成员变量,也就是说把Slice变成了std::string类型std::string rep_;
public:// 构造函数,这个没参数,但是可以通过DecodeFrom()再设置InternalKey() { }// 内部键包含:用户指定的键Slice、顺序号、以及值类型InternalKey(const Slice& user_key, SequenceNumber s, ValueType t) {// 最终的内部键的格式为:[Slice]+[littleendian(SequenceNumber<<8 + ValueType)],代码实现读者自己看吧 AppendInternalKey(&rep_, ParsedInternalKey(user_key, s, t));}// 从一个Slice中解码内部键,就是直接按照字符串赋值,所以此处的Slice非user_key// 而是别的InternalKey.Encode()接口输出的void DecodeFrom(const Slice& s) { rep_.assign(s.data(), s.size()); }// 把内部键编码成Slice格式,其实就是用Slice封装一下,注意调用这个函数后InternalKey对象不能析构// 因为Slice不负责内存管理Slice Encode() const {assert(!rep_.empty());// 直接返回std::string类型?上面我们介绍Slice时候说过,Slice有一个构造函数参数就是std::string类型// 并且没有声明为explicit,所以可以将std::string赋值给Slicereturn rep_;}// 返回用户键值,所以需要按照上面构造函数的格式提取出来,ExtractUserKey()读者自己看就行,很简单Slice user_key() const { return ExtractUserKey(rep_); }// ParsedInternalKeyd的类型下面会有介绍,比较简单void SetFrom(const ParsedInternalKey& p) {rep_.clear();// 这个函数调用和构造函数里面调用的是一个函数,所以就不多说了AppendInternalKey(&rep_, p);}// 清空接口void Clear() { rep_.clear(); }
};

从代码我们可以得出一个结论,内部键格式是在用户指定的键(Slice)基础上追加了按照小端方式存储的(顺序号<<8+值类型),即便是用std::string类型存储的,但是已经不再是纯粹的字符串了。

LookupKey

当需要在leveldb查找对象的时候,查找顺序是从第0层到第n层遍历查找,找到为止(最新的修改或者删除的数据会优先被找到,所以不会出现一个键有多个值的情况)。由于不同层的键值不同,所以LookupKey提供了不同层所需的键值。

// 代码源自leveldb/db/dbformat.h
// 因为查找可能需要查找memtable和sst,所以存储的内容要包含多种存储结构的键值
class LookupKey {
public:// 构造函数需要用户指定的键以及leveldb内部LookupKey(const Slice& user_key, SequenceNumber sequence) {// 获取用户指定的键的长度size_t usize = user_key.size();// 为什么扩展了13个字节,其中8字节(64位整型顺序号<<8+值类型)+5字节(内部键的长度Varint32)size_t needed = usize + 13;  // A conservative estimate// 内部有一个固定大小的空间,200个字节,这样可以避免频繁的内存分配,因为200个字节可以满足绝大部分需求char* dst;if (needed <= sizeof(space_)) {dst = space_;} else {dst = new char[needed];}// 记录一下起始地址,在对象析构的时候需要释放空间(如果是从堆上申请的空间)start_ = dst;// 起始先存储内部键的长度,这个长度是Varint32类型dst = EncodeVarint32(dst, usize + 8);// 接着就是用户指定的键值kstart_ = dst;memcpy(dst, user_key.data(), usize);dst += usize;// 最后是64位的(顺序号<<8|值类型),此处值类型是kValueTypeForSeek,和类名LookupKey照应上了EncodeFixed64(dst, PackSequenceAndType(s, kValueTypeForSeek));dst += 8;// 记录结束位置,可以用于计算各种类型键值长度end_ = dst;// 整个存储空间的结构为[内部键大小(Varint32)][用户指定键][顺序号<<8|值类型]}// 析构函数,因为有申请内存的可能性,所以析构函数还是要有的~LookupKey() {// 要判断一下内存是否为堆上申请的,如果是就释放内存if (start_ != space_) delete[] start_;}// 获取memtable需要的键值,从这里我们知道memtable需要的是内存中全部内容Slice memtable_key() const { return Slice(start_, end_ - start_); }// 获取内部键Slice internal_key() const { return Slice(kstart_, end_ - kstart_); }// 获取用户指定键Slice user_key() const { return Slice(kstart_, end_ - kstart_ - 8); }private:const char* start_;  // 指向存储空间的起始位置const char* kstart_; // 指向用户指定键/内部键的起始位置const char* end_;    // 指向键值的结尾char space_[200];    // 这样可以避免频繁申请内存
};

Comparator

Comparator是个抽象类,主要用于数据键值比较的,毕竟leveldb是按照键有序存储的,所以比较器算是一个比较通用的类型。并且,leveledb里面定义的比较器有两个特殊函数接口挺有意思的,具体我们从代码定义上来看看:

// 代码源自leveldb/include/leveldb/comparator.h
class Comparator {
public:// 虚析构函数,析构对象的时候会调用实现类的析构函数virtual ~Comparator();// 比较,这个和Slice的比较是一个意思,所以基本上就是用Slice.compare()实现的,直接用Slice.compare()不就完了么// 为什么还要定义这个类呢,后面会有各种实现类读者就知道为什么了virtual int Compare(const Slice& a, const Slice& b) const = 0;// 获取比较器的名字,这个不是很重要,也就是每个比较器有一个名字,我看基本都是实现类的全名(含namespace)virtual const char* Name() const = 0;// 通过函数名有点看不出来什么意思,这个函数需要实现的功能是找到一个最短的字符串,要求在[*start,limit)区间// 这是很有意思的功能,当需要找到以某些字符串为前缀的所有对象时,就会用到这个接口,等我们看到的时候会重点解释使用方式// 如果比较简单的比较器实现可以实现为空函数virtual void FindShortestSeparator(std::string* start, const Slice& limit) const = 0;// 这个接口和上一个接口很像,只是输出最短的比*key大的字符串,没有区间限制,如果比较简单的比较器可以实现为空函数virtual void FindShortSuccessor(std::string* key) const = 0;
};

因为FindShortestSeparator()和FindShortSuccessor()这两个接口看似与比较器没啥关系,但是这两个接口实现的功能严重依赖比较功能,所以leveldb做到了比较器里面。我搜遍了leveldb的所有源码,继承Comparator的类只有三个:leveldb_comparator_t,InternalKeyComparator和BytewiseComparatorImpl。其中最为重要的当属BytewiseComparatorImpl,我们需要先从这个类型入手,然后再介绍其他的类型,等看完后大家就明白为什么BytewiseComparatorImpl是最重要的了。

BytewiseComparatorImpl

因为Slice并没有规定Key具体类型,所以leveldb是支持用户自定义比较器的,在创建leveldb数据库对象的时候通过Option指定。大家需要注意一点,Option的构造函数默认是把比较器设定为BytewiseComparatorImpl的,也就是说BytewiseComparatorImpl是leveldb默认的比较器,所以我说它比较重要。直接上代码吧:

// 代码源自leveldb/util/options.cc
// 下面是Options的构造函数,我们可以看出comparator赋值是BytewiseComparator()的返回值
// BytewiseComparator()返回的就是BytewiseComparatorImpl对象指针
Options::Options(): comparator(BytewiseComparator()),...... {
}
// 代码源自leveldb/util/comparator.cc
// Bytewise顾名思义按照字节主意比较,所以原理比较简单,下面就是具体实现了
class BytewiseComparatorImpl : public Comparator {
public:// 空的构造函数,也是,这种对象也不需要啥成员变量BytewiseComparatorImpl() { }// 实现获取名字的接口virtual const char* Name() const {return "leveldb.BytewiseComparator";}// 比较本身就依赖Slice.compare()函数就可以了,本身Slice.compare()就是用memcmp()实现的,符合按字节比较的初心virtual int Compare(const Slice& a, const Slice& b) const {return a.compare(b);}// 在Comparator定义的时候就感觉就比较模糊,这回看看是怎么实现的?virtual void FindShortestSeparator(std::string* start, const Slice& limit) const {// 二者取最小的长度,避免越界访问size_t min_length = (std::min)(start->size(), limit.size());// 名字比较明确,就是第一个字节值不同的索引值,初始为0,这一点应该比较好理解,如果和limit相同,+1肯定就比limit大了// 所以要找到第一个和limit不同的字节,从理论上讲,应该*start<=limit,第一个不同的字符就是第一个比limit字节小的数// 当然也不排除有传错参数的情况size_t diff_index = 0;// 找到第一个不相同的字节值的位置while ((diff_index < min_length) && ((*start)[diff_index] == limit[diff_index])) {diff_index++;}// 也就是说*start和limit是相同的或者说limit是以*start为前缀的,所以肯定找不到一个字符串比*start大还比limit小if (diff_index >= min_length) {} else {// 取出这个字节值uint8_t diff_byte = static_cast<uint8_t>((*start)[diff_index]);// 这个字节不能是0xff,同时+1后也也要比limit相应字节小,至少能看出来0xff对于leveldb是有特殊意义的if (diff_byte < static_cast<uint8_t>(0xff) && diff_byte + 1 < static_cast<uint8_t>(limit[diff_index])) {// 把找到的那个字符+1,同时把字符串缩短到那个不同字符的位置,这样就是[*start,limit)区间最短的字符串了(*start)[diff_index]++;start->resize(diff_index + 1);assert(Compare(*start, limit) < 0);}// 其他情况就不对*start做任何修改,认为*start本身就是这个字符串了,那我们举一个例子:// *start=['a', 'a', 'a', 'c', 'd', 'e']//  limit=['a', 'a', 'a', 'b', 'd', 'e']// 上面这种情况是我认为输出*start=['a', 'a', 'a', 'c', 'e']可能会更好,为什么此处不这么做呢?// 除非就是用在列举以某个字符串为前缀的对象时候使用,找到第一个比前缀大的字符串}}// 找到第一个比*key大的最短字符串virtual void FindShortSuccessor(std::string* key) const {// 获取键的长度size_t n = key->size();// 遍历键的每个字节for (size_t i = 0; i < n; i++) {// 找到第一个不是0xff的字节const uint8_t byte = (*key)[i];if (byte != static_cast<uint8_t>(0xff)) {// 把这个位置的字节+1,然后截断就可以了,算是比较简单。(*key)[i] = byte + 1;key->resize(i+1);return;}}// 能到这里说明*key全部都是0xff,也就找不到相应的字符串了}
};

BytewiseComparatorImpl实现比较简单,没有什么太复杂的逻辑,所以就不再多说写什么了。但是读者还是需要注意一下,上面很多地方提到了字符串,而且是用std::string作为类型,其实数据中可能存在'\0'。在很多情况需要用一些特殊的字符做分割,比如std::string a=['a', 'b', 'c', '\0', 'd'],a的长度是5而不是3。只是强行转换为const char*时表现为“abc”而已,这本身是char*的自身约束(0是字符串的结尾)。所以以后大家要习惯这种所谓的“字符串”,并且BytewiseComparatorImpl这种字节比较器名字还是比较贴切的。

InternalKeyComparator

顾名思义,主要用在内部的Key比较器,范围也确定了,功能也确定了。其实InternalKeyComparator的主要功能还是通过BytewiseComparatorImpl实现,只是在BytewiseComparatorImpl基础上做了一点扩展。

// 代码源自leveldb/db/dbformat.h
class InternalKeyComparator : public Comparator {
private:// 这个是最关键的了,有一个用户定义的比较器,不用多说了,肯定是BytewiseComparatorImpl的对象啦// 所以我前面说InternalKeyComparator绝大部分是通过BytewiseComparatorImpl实现的const Comparator* user_comparator_;
public:// 构造函数需要提供比较器对象explicit InternalKeyComparator(const Comparator* c) : user_comparator_(c) { }// 返回比较器的名字virtual const char* Name() const {return "leveldb.InternalKeyComparator";}// 比较两个Slicevirtual int Compare(const Slice& a, const Slice& b) const {// 采用BytewiseComparatorImpl.compare()进行比较,但是比较的是刨除顺序ID的用户提供的键// 为什么要刨除顺序ID呢?看看下面的注释就知道了int r = user_comparator_->Compare(ExtractUserKey(akey), ExtractUserKey(bkey));// 用户提供的键相等,意味着遇到了对象删除或者修改操作,需要再比较一下序列号。if (r == 0) {// 要返序列化(解码)顺序号const uint64_t anum = DecodeFixed64(akey.data() + akey.size() - 8);const uint64_t bnum = DecodeFixed64(bkey.data() + bkey.size() - 8);// 顺序ID越大,说明插入时间越晚,数据就越新,如果排序的话应该排在前面,这个在LSM算法有说明// 这就是为什么要针对顺序ID要特殊处理一下if (anum > bnum) {r = -1;} else if (anum < bnum) {r = +1;}}return r;}// 获取一个最短的字符串在[*start,limit)范围内virtual void FindShortestSeparator(std::string* start, const Slice& limit) const {// 先把顺序号去了,把用户传入的key提取出来Slice user_start = ExtractUserKey(*start);Slice user_limit = ExtractUserKey(limit);// 调用比较器的FindShortestSeparator()会修改传入参数,所以先存在临时变量中std::string tmp(user_start.data(), user_start.size());// 调用BytewiseComparatorImpl.FindShortestSeparator()user_comparator_->FindShortestSeparator(&tmp, user_limit);// 看看是否找到了字符串,因为只要找到了那个字符串,长度肯定会被截断的。if (tmp.size() < user_start.size() && user_comparator_->Compare(user_start, tmp) < 0) {// 追加顺序ID,此时的顺序ID没什么大作用,所以用最大顺序ID就可以PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek));assert(this->Compare(*start, tmp) < 0);assert(this->Compare(tmp, limit) < 0);start->swap(tmp);}}// 获取一个比*key大的最短字符串,原理和FindShortestSeparator很相似,我就不做重复注释了virtual void FindShortSuccessor(std::string* key) const {Slice user_key = ExtractUserKey(*key);std::string tmp(user_key.data(), user_key.size());user_comparator_->FindShortSuccessor(&tmp);if (tmp.size() < user_key.size() && user_comparator_->Compare(user_key, tmp) < 0) {PutFixed64(&tmp, PackSequenceAndType(kMaxSequenceNumber,kValueTypeForSeek));assert(this->Compare(*key, tmp) < 0);key->swap(tmp);}}// 获取用户传入的比较器指针const Comparator* user_comparator() const { return user_comparator_; }// 比较内部键,内部键存储格式就是[用户键][顺序号<<8|值类型],所以重新封装成Slice后复用上面提到的比较函数int Compare(const InternalKey& a, const InternalKey& b) const {return Compare(a.Encode(), b.Encode());}
};

看上面的代码就明白为什么叫做InternalKeyComparator了,因为leveldb内部存储键的时候在用户提供的键之上追加了顺序号,所以比较的时候略有不同。

leveldb_comparator_t

这个是转为C接口设计的比较器,非常简单,读者自己看代码了解一下。

Iterator

用过标准库(stl)容器的对迭代器不会陌生,用于遍历(可以条件遍历)容器中的元素使用。在leveldb中也有迭代器,毕竟leveldb可以想象成一个容量更大、功能更强的std::map嘛。在leveldb中,对Iterator进行了抽象,不同的存储类型自行实现具体的迭代器,我们这里只对Iterator的每个接口的功能做说明,具体实现会在其他文章中说明。

// 代码源自leveldb/include/leveldb/iterator.h
class Iterator {// 为了方便理解构造函数和析构函数,代码上把私有成员变量和类型定义放到前面
private:// 定义清理类型struct Cleanup {CleanupFunction function; // 清理函数,类型定义下面有定义void* arg1;               // 清理函数的参数1void* arg2;               // 清理函数的参数2Cleanup* next;            // 单向链表,指向下一个清理对象};// 所有需要清理的内容Cleanup cleanup_;public:// 构造函数,初始是没有任何需要清理的对象的Iterator() {cleanup_.function = NULL;cleanup_.next = NULL;}// 析构函数virtual ~Iterator() {// 看看是否有任何需要清理的对象if (cleanup_.function != NULL) {// 清理掉对象(*cleanup_.function)(cleanup_.arg1, cleanup_.arg2);// 遍历链表上的其他清理对象,逐一清理for (Cleanup* c = cleanup_.next; c != NULL; ) {(*c->function)(c->arg1, c->arg2);Cleanup* next = c->next;delete c;c = next;}}}// 获取迭代器当前是否正常,比如到了结束为止该函数就会返回falsevirtual bool Valid() const = 0;// 定位到第一个对象为止virtual void SeekToFirst() = 0;// 定位到最后一个对象位置virtual void SeekToLast() = 0;// 定位到Slice指定的对象位置,如果没有对象,那么Valid()返回false.virtual void Seek(const Slice& target) = 0;// 定位到下一个对象,等同于stl容器迭代器的++virtual void Next() = 0;// 定位到前一个对象,等同于stl容器迭代器的--virtual void Prev() = 0;// 获取迭代器当前定位对象的键,前提是Valid()返回truevirtual Slice key() const = 0;// 获取迭代器当前定位对象的值,前提是Valid()返回truevirtual Slice value() const = 0;// 返回当前的状态virtual Status status() const = 0;// 定义清理函数类型typedef void (*CleanupFunction)(void* arg1, void* arg2);// 注册清理对象void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2) {assert(func != NULL);Cleanup* c;// 如果当前清理对象链表是空的,那就把当前的清理对象记录在表头if (cleanup_.function == NULL) {c = &cleanup_;} else {// 创建新的清理对象放在表头的下一个位置,因为没有规定清理顺序,所以这种做法效率最高c = new Cleanup;c->next = cleanup_.next;cleanup_.next = c;}// 记录清理对象的清理函数和参数c->function = func;c->arg1 = arg1;c->arg2 = arg2;}
};

迭代器的定义还是比较简单的,相比于stl增加了清理对象的内容,我会在其他章节说明都清理什么内容,这主要看具体的存储实现才行。

AtomicPointer

原子指针是通过原子操作访问的指针(不是访问指针指向的内存值,是指针本身的值),相比于普通指针(比如void*),对指针的获取和设置都支持原子操作的接口,同时也支持普通的访问方式。我们来看看AtomicPointer的定义:

// 代码源自leveldb/port/atomic_pointer.h
// leveldb/port目录是根据系统、CPU的不同自行实现的适配目录,所以我们只讲接口,不讲实现
class AtomicPointer {
private:// 指针void* rep_;public:// 默认构造函数AtomicPointer() { }// 提供指针的构造函数explicit AtomicPointer(void* p) : rep_(p) {}// 正常方式的获取/设置指针,比较简单,不用多说了inline void* NoBarrier_Load() const { return rep_; }inline void NoBarrier_Store(void* v) { rep_ = v; }// 原子获取指针是通过内存屏障实现的,leveldb的内存屏障是阻止编译器乱序,但不能阻止CPU乱序执行(在SMP体系下)// 此处使用内存屏障的最主要原因是因为该函数是inline,在不知道使用函数的上下代码的情况下,可能会被编译器优化掉// 我们这里举一个比较简单的例子:void* ptr = NULL;//                            ......(等待其他线程设置指针)//                            ptr = Acquire_Load();// 此时从编译器的角度来看ptr和后面的代码没有任何依赖,那么编译器可以先赋值ptr在等待其他线程设置// 这个本身就完全改变了我们想要的结果inline void* Acquire_Load() const {void* result = rep_;MemoryBarrier();return result;}// 原子设置指针是通过内存屏障实现的,同样的道理,因为是inline函数,此处的v可能会在调用本函数前被其他线程修改// 此处的内存屏障就可以保证赋值语句不会被编译器优化而被提前执行inline void Release_Store(void* v) {MemoryBarrier();rep_ = v;}
};

从代码上看,是否采用内存屏障访问指针,感觉都没什么区别,因为二者都是inline,不存在调用函数带来的开销,二者的区别在于使用时的上下文。所以,我在其他介绍leveldb的文章中但凡用到源自指针的地方我都会说明为什么使用带有内存屏障的方式访问指针。

深入浅出leveldb之基础知识相关推荐

  1. 100天精通Python丨基础知识篇 —— 05、7大基础数据类型详解(变量、列表、集合、字典、元组)

    <100天精通Python专栏 - 快速入门到黑科技>是由 CSDN 内容合伙人丨全站排名 Top 4 的硬核博主 不吃西红柿 倾力打造,专栏分为基础知识篇和黑科技应用篇.基础知识篇以理论 ...

  2. 统计学之基础知识(一)

    参考资料:可汗学院统计学.深入浅出统计学 统计学基础知识 1.平均数 平均值就是用数据之和除以数据的个数 2.方差 偏差=数据的数值-平均值 方差=[(偏差的平方)的合计]/(数据数) 3.标准差 标 ...

  3. 深入浅出Pytorch:02 PyTorch基础知识

    深入浅出Pytorch 02 PyTorch基础知识 内容属性:深度学习(实践)专题 航路开辟者:李嘉骐.牛志康.刘洋.陈安东 领航员:叶志雄 航海士:李嘉骐.牛志康.刘洋.陈安东 开源内容:http ...

  4. 深入浅出Yolo系列之Yolov3Yolov4Yolov5核心基础知识完整讲解

    深入浅出Yolo系列之Yolov3&Yolov4&Yolov5核心基础知识完整讲解

  5. JNI学习开始篇 基础知识 数据映射及学习资料收集

    JNI学习开始篇 基础知识 数据映射及学习资料收集 JNI介绍 JNI(Java Native Interface) ,Java本地接口. 用Java去调用其他语言编写的程序,比如C或C++. JNI ...

  6. [Python从零到壹] 八.数据库之MySQL和Sqlite基础知识及操作万字详解

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  7. poll和死锁_计算机基础知识

    转自: http://blog.csdn.net/qq_15437629/article/details/52388685 在这里只做备份 计算机网络 TCP/IP 模型 TCP/IP协议集的分层实施 ...

  8. 计算机知识太多了,计算机基础知识对程序员来说有多重要?

    原标题:计算机基础知识对程序员来说有多重要? 科班和培训生同比于自学者的优势就在于这些计算机专业的核心课程(数据结构与算法这种不管科班培训都要学的不算):离散数学.编译原理.计算机组成原理.操作与系统 ...

  9. 学习算法你必须知道的一些基础知识(文末福利)

    点击标题下「异步社区」可快速关注 机器学习是解决很多文本任务的基本工具,本文自然会花不少篇幅来介绍机器学习.要想搞明白什么是机器学习,一定要知道一些概率论和信息论的基本知识,本文就简单回顾一下这些知识 ...

最新文章

  1. 组队学习:学习者参考手册
  2. 广州限购后首场车展明日开幕
  3. .htm .html .shtml的区别
  4. ZOJ3865:Superbot(BFS) The 15th Zhejiang University Programming Contest
  5. 【前端攻略】最全面的水平垂直居中方案与flexbox布局
  6. Win7/Win8.1升级Win10后屏幕一直闪烁怎么办?
  7. Appium 服务器参数
  8. 从入门到深入Fiddler 2 (二)
  9. 【Objective-C】Http常用API、同步请求与异步请求[转]
  10. Android闹钟程序周期循环提醒源码(AlarmManager)【转】
  11. TCP/IP协议详解内容总结(怒喷一口老血)
  12. CSS基础——CSS字体样式属性【学习笔记】
  13. 读书笔记∣写给大家看的设计书
  14. 传奇开服教程:传奇添加地图花屏原因与解决方法
  15. linux drwxr-xr-x 是什么意思
  16. matlab的syms无法在函数中使用_matlab syms什么意思_常见问题解析
  17. 3个开源音乐播放器:Aqualung,Lollypop和GogglesMM
  18. 干货 I 用数据分析进行“无死角”的复盘?
  19. warning:4005 DXGI_STATUS_OCCLUDED,宏重定义
  20. BrandHouse在蓝筹中国基金领投的首轮融资中筹得400万欧元

热门文章

  1. Spring Cloud Netfilx Ribbon(负载均衡工具)
  2. linux命令之一 diff(2) 命令用法
  3. xm在线转换成mp3_也就索尼敢了!4000多卖QQ音乐还不送会员,安卓MP3咋这么贵?...
  4. 《Weighted Maximum Mean Discrepancy for Unsupervised Domain Adaptation》论文阅读
  5. java.util.Date java.sql.Date SimpleDateFormat String 转DATE
  6. Android 应用层组件安全测试基础实战技巧
  7. Intellij Idea整合JProfiler插件
  8. 尚硅谷 宋红康 JVM教程_01_内存与垃圾回收篇——02
  9. 巨人的进击 —— Android生态的破与立
  10. 弘晖资本募集完成人民币三期基金