LevelDB(一/二)
githut中包含源码的目录如下:
- benchmarks:
- db:
- helpers/memenv:
- include/leveldb:
- port:
- table:
- util:
一、port
port目录下主要有两个文件:thread_annotations.h
和port_stdcxx.h
。
thread_annotations.h
中定义了线程安全注解,主要使用的注解:
GUARDED_BY
:PT_GUARDED_BY
:
port_stdcxx.h
中封装了两个类,Mutex
锁类和CondVar
条件变量类。
leveldb扩展1:std::lock_guard 和 std::unique_lock
std::lock_guard
类lock_guard
是互斥体包装器,为在作用域块期间占有互斥提供便利 RAII 风格机制。
创建 lock_guard
对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard
对象的作用域时,销毁 lock_guard
并释放互斥。
std::unique_lock
unique_lock
实现了lock_guard
类似的功能,但unique_lock
保证了只有一个unique_lock
拥有mutex
,同时提供了转移拥有权的接口。
- lock_guard在构造时或者构造前(std::adopt_lock)就已经获取互斥锁,并且在作用域内保持获取锁的状态,直到作用域结束;而unique_lock在构造时或者构造后(std::defer_lock)获取锁,在作用域范围内可以手动获取锁和释放锁,作用域结束时如果已经获取锁则自动释放锁。
- lock_guard锁的持有只能在lock_guard对象的作用域范围内,作用域范围之外锁被释放,而unique_lock对象支持移动操作,可以将unique_lock对象通过函数返回值返回,这样锁就转移到外部unique_lock对象中,延长锁的持有时间。
原文链接:https://blog.csdn.net/mrbone11/article/details/121620329
在CondVar
类中,使用了std::unique_lock
:
std::unique_lock<std::mutex> lock(mu_->mu_, std::adopt_lock);
std::unique_lock
有两个成员变量_M_owns
(表示是否拥有锁)和 _M_device
(锁对象),
- 当
_M_owns
为true并且_M_device
不为空时表示当前对象拥有_M_device
并且将_M_device
加锁; - 当
_M_owns
为false并且_M_device
不为空时当前锁未加锁; - 当
_M_owns
为false并且_M_device
为空时表示当前对象为空; - 其他情况为不合法。
常用构造函数:
unique_lock()
将_M_owns
置为false 且_M_device
置为空。unique_lock(mutex_type& __m)
会将_M_owns
置为false, 初始化_M_device
锁对象,并且将_M_device
加锁并拥有_M_device
对象。unique_lock(mutex_type& __m, defer_lock_t)
会将_M_owns
置为false, 初始化_M_device
锁对象。在使用的过程中可以自定义unique_lock
锁住的范围。unique_lock(mutex_type& __m, try_to_lock_t)
会将_M_owns
置为尝试加锁的结果, 初始化_M_device
锁对象。unique_lock(mutex_type& __m, adopt_lock_t)
会将_M_owns
置为true, 初始化_M_device
锁对象。要求提前对_M_device
加锁。
以下为源码便于理解:
typedef _Mutex mutex_type;
unique_lock() noexcept
: _M_device(0), _M_owns(false)
{ }
explicit unique_lock(mutex_type& __m)
: _M_device(std::__addressof(__m)), _M_owns(false)
{
lock();
_M_owns = true;
}
unique_lock(mutex_type& __m, defer_lock_t) noexcept
: _M_device(std::__addressof(__m)), _M_owns(false)
{ }
unique_lock(mutex_type& __m, try_to_lock_t)
: _M_device(std::__addressof(__m)), _M_owns(_M_device->try_lock())
{ }
unique_lock(mutex_type& __m, adopt_lock_t) noexcept
: _M_device(std::__addressof(__m)), _M_owns(true)
{
// XXX calling thread owns mutex
}
void lock()
{
if (!_M_device)
__throw_system_error(int(errc::operation_not_permitted));
else if (_M_owns)
__throw_system_error(int(errc::resource_deadlock_would_occur));
else
{
_M_device->lock();
_M_owns = true;
}
}
~unique_lock()
{
if (_M_owns)
unlock();
}
关于unlock()和release():
unlock()
只是针对于 当前unique_lock
对象持有的锁进行解锁的操作。release()
是返回unique_lock
对象持有的锁,并且将当前unique_lock
对象的成员置空(也就是常说的释放所有权)。
void unlock()
{
if (!_M_owns)
__throw_system_error(int(errc::operation_not_permitted));
else if (_M_device)
{
_M_device->unlock();
_M_owns = false;
}
}
mutex_type* release() noexcept
{
mutex_type* __ret = _M_device;
_M_device = 0;
_M_owns = false;
return __ret;
}
try_lock()
本质中是在合法性判断之后对持有锁对象调用try_lock()
方法。
所有权的传递
unique_lock(const unique_lock &) = delete;
unique_lock &operator=(const unique_lock &) = delete;
unique_lock
的拷贝构造和赋值构造是删除。
std::unique_lock<std::mutex> ulock1(ulock);//error
unique_lock(unique_lock &&__u) noexcept
: _M_device(__u._M_device), _M_owns(__u._M_owns)
{
__u._M_device = 0;
__u._M_owns = false;
}
unique_lock &operator=(unique_lock &&__u) noexcept
{
if (_M_owns)
unlock();
unique_lock(std::move(__u)).swap(*this);
__u._M_device = 0;
__u._M_owns = false;
return *this;
}
但是可以使用移动语义,对右值进行所有权的传递。
- std::move()
std::unique_lock<std::mutex> ulock2(std::move(ulock)); // success!
- 局部变量(右值)
std::unique_lock<std::mutex> tmp_unique_lock()
{
std::unique_lock<std::mutex> ulock3(lock);
return ulock3;
}
int main
{
std::unique_lock<std::mutex> ulock4 = tmp_unique_lock();
}
二、util
1. arrena.h
快速管理内存,用于高频次的内存分配和释放,主要用在memtable和immemtable上。
并不是每次分配内存都需要new出来。
默认分配一个整块的大小为kBlockSize
4kb。
// 内存池中,指向已使用内存位置的指针
char* alloc_ptr_;
// 当前内存剩余内存字节数
size_t alloc_bytes_remaining_;
// 实际分配的内存池
std::vector<char*> blocks_;
// 记录内存池中已内存的大小
std::atomic<size_t> memory_usage_;
// 一个块大小(4k)
static const int kBlockSize = 4096;
// 初始化alloc_ptr_ 、alloc_bytes_remaining_、memory_usage_变量。
Arena();
// 对已分配的内存进行释放。
~Arena();
// 具体执行内存分配的函数
/*
主要逻辑是:
new出来`block_bytes`大小的内存,把地址的指针加到`blocks_`数组中(便于析构),新分配的内存大小加到`memory_usage_`变量中。
返回当前分配内存的首地址。
*/
char* AllocateNewBlock(size_t block_bytes);
// 重新分配内存
// 注:调用这个函数的前提就是现有的内存`alloc_bytes_remaining_`不够bytes.
/*
主要逻辑是:
先对要分配的内存大小bytes进行判断,
- 如果大于块容量的1/4 1024 1kb ,就直接在堆上分配一块bytes大小的内存,返回地址结束,不改变当前的`alloc_ptr_` 、`alloc_bytes_remaining_`变量。
- 否则,之前剩下的内存`alloc_bytes_remaining_`就不要了,重新分配`kBlockSize`大小的内存`result`,然后在`alloc_ptr_ = result; alloc_ptr_ += bytes ` ,返回`result`。
*/
char* AllocateFallback(size_t bytes);
//分配bytes大小的内存
/*
主要逻辑是:
先对传入的bytes大小的内存进行判断,
- 如果之前分配的内存块所剩余的内存可以分配bytes大小的话,就直接更新`alloc_ptr_` 、`alloc_bytes_remaining_`变量,因为没有再额外分配内存,所以`memory_usage_`变量不变。
- 如果不够分配bytes大小,调用`AllocateFallback(size_t bytes)`方法。就是`AllocateFallback`方法中注的原因。
*/
char* Allocate(size_t bytes);
// 对齐(偶数)方式的分配内存。
/*
主要逻辑是:
- 计算当前机器的位数`align`
- `(align & (align - 1)) == 0`用这个语句判断`align`变量是偶数 (高级!!!)
- `reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1) == 0` 判断`alloc_ptr_`指针是否对于`align`对齐。(如果是64位8字节,那么对齐的地址一定是8的倍数,xx...xx1000,那么对于 ` (align - 1) = 7` 111, `alloc_ptr_`指针的地址与 ` (align - 1) `相与必为0)
- 计算补齐位`slop` 和 总共需要分配的内存` need = slop + bytes`
- 对`need `与剩余的内存 `alloc_bytes_remaining_`判断
- 如果剩余的内存足够,直接更新`alloc_ptr_` 、`alloc_bytes_remaining_`变量
- 否则调用`AllocateFallback(size_t bytes)`方法.
- 最后再确认是否对齐
*/
char* AllocateAligned(size_t bytes);
// 返回内存池中已使用内存的大小
size_t MemoryUsage() const;
leveldb扩展2:reinterpret_cast
通过重新解释底层位模式在类型间转换。
reinterpret_cast 用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。
不可去除常量const
便于好记,笔者缩略概括了几种使用场景:
- 自己=自己:整型、枚举、指针或成员指针类型的表达式可以转换到它自身的类型。
- 指针 --> int(32位) long (64位) std::uintptr_t
- 整数 / 枚举 --> 足够大小的指针
- 任何 std::nullptr_t 类型的值,包含 nullptr --> 整型
- 任何对象指针类型 T1* 都可以转换到指向对象指针类型 T2*。
- T1 类型的左值表达式可转换成到另一个类型 T2 的引用。
- 不同函数类型的指针之间的转换。
- 函数指针可以转换成 void* 或任何其他对象指针,反之亦然。
- 任何指针类型的空指针值都可以转换到任何其他指针类型的空指针值。
- 成员函数指针可以转换成指向不同类型的不同成员函数的指针。
- 指向某类 T1 的成员对象的指针可以转换成指向另一个类 T2 的另一个成员对象的指针。
详细说明请参考:https://zh.cppreference.com/w/cpp/language/reinterpret_cast
2. config.h
// 从32位映射到4位,只是用了uint8_t。就是从高到低每8位 -->1位
/*
buffer[0] = static_cast<uint8_t>(value);
buffer[1] = static_cast<uint8_t>(value >> 8);
buffer[2] = static_cast<uint8_t>(value >> 16);
buffer[3] = static_cast<uint8_t>(value >> 24);
*/
void EncodeFixed32(char* dst, uint32_t value)
// 从64位映射到8位。就是从高到低每8位 -->1位
// 同上
void EncodeFixed64(char* dst, uint64_t value);
//从4位映射到32位。就是从低到高每1位 --> 8位
/*
return (static_cast<uint32_t>(buffer[0])) |
(static_cast<uint32_t>(buffer[1]) << 8) |
(static_cast<uint32_t>(buffer[2]) << 16) |
(static_cast<uint32_t>(buffer[3]) << 24);
用 `|` 去累增结果
*/
uint32_t DecodeFixed32(const char* ptr);
// 从8位映射到64位。就是从低到高每1位 --> 8位
// 同上
uint64_t DecodeFixed64(const char* ptr);
// 把p指向的小于等于4位的char转换成uint32_t
// 查找调用这个函数的地方,传入的参数 p 是一个字符串数据的起始地址, limit是字符串最长的地址,value一般为0。
/*
主要逻辑是:
- 定义了结果`result`
- `shift`是7的倍数 在0-35位的循环 且 小于字符串长度
- `byte`是p指向的值,
- 如果比128大,就超过了aci码的限制(char的范围),只保留后7位的值,并向左偏移
- 直到比128小,结果加上当前指针指向的值,左移`shift`位, 返回结果
- 不合法返回空指针
*/
char* GetVarint32PtrFallback(const char* p, const char* limit, uint32_t* value);
// 把p指向的小于等于4位的char转换成uint32_t 4->32
// 查找调用这个函数的地方,传入的参数 p 是一个字符串数据的起始地址, limit是字符串最长的地址,value一般为0。
/*
- 如果当前传入的参数合法(字符串的开始地址小于其结束地址) 并且 p指向的值比128小(在char范围内),value保存p的值
- 返回p下一个位置指向的值
- 如果p指向的值比128大,调用`GetVarint32PtrFallback`函数
*/
const char* GetVarint32Ptr(const char* p, const char* limit, uint32_t* value);
// 把p指向的小于等于10位的char转换成uint64_t
// 查找调用这个函数的地方,传入的参数 p 是一个字符串数据的起始地址, limit是字符串最长的地址,value一般为0。
/*
主要逻辑是:
- 定义了结果`result`
- `shift`是7的倍数 在0-35位的循环 且 小于字符串长度
- `byte`是p指向的值,
- 如果比128大,就超过了aci码的限制(char的范围),只保留后7位的值,并向左偏移
- 直到比128小,结果加上当前指针指向的值,左移`shift`位, 返回结果
- 不合法返回空指针
*/
const char* GetVarint64Ptr(const char* p, const char* limit, uint64_t* v);
// 在字符串`dst`后新增`value`的压缩编码32->4
void PutFixed32(std::string* dst, uint32_t value);
// 在字符串`dst`后新增`value`的压缩编码64->8
void PutFixed64(std::string* dst, uint64_tvalue);
// 基于 uint32 类型的值通过循环将其每一位都编码到字节序列`dst`中。
// 32->4
char* EncodeVarint32(char* dst, uint32_t v);
// 基于 uint64_t 类型的值通过循环将其每一位都编码到字节序列`dst`中。
// 64->8
char* EncodeVarint64(char* dst, uint64_t v);
// 把`uint32_t v`的编码附在字符串`dst`后.
void PutVarint32(std::string* dst, uint32_t v);
// 把`uint64_t v`的编码附在字符串`dst`后.
void PutVarint64(std::string* dst, uint64_t v);
// 把`value.size()`编码附在字符串`dst`后.
// dst后加上 value的数据 和 value的长度
// dst + value.size + value.data + value.size
void PutLengthPrefixedSlice(std::string* dst, const Slice& value);
// 返回将`uint64_t v`转换成char字节序的长度
int VarintLength(uint64_t v);
// input的解析长度也可能小于4位
// 解析input的前4位 len 表示长度
// input是 input从第四位开始 len长度的数据
// value保存前面解析的长度
/*
- 解析前四位之后,下一位没有数据,返回false
- input是 input从第四位开始 len长度的数据
*/
bool GetVarint32(Slice* input, uint32_t* value);
// 同上
bool GetVarint64(Slice* input, uint64_t* value);
// 先解析input的前4位长度部分 input+=4
// result保存input前4位长度的数据
// 将input+=前4位长度的数据
bool GetLengthPrefixedSlice(Slice* input, Slice* result);
3.mutexlock.h
MutexLock类,自动加锁解锁,类似于lockguard。
4.no_destructor.h
NoDestructor模板类。
声明一个变量的析构函数永远不会调用,一般还会配合单例模板类使用。
// 构造函数 传入可变参数列表
/*
1. 检查InstanceType的大小
2. 检查InstanceType的对齐方式大小
3. 在instance_storage_对象上创建InstanceType对象
*/
template <typename... ConstructorArgTypes>
explicit NoDestructor(ConstructorArgTypes&&... constructor_args);
// 返回单例对象
InstanceType* get();
// 在c++ 17之后使用 std::launder和std::destroy_at
// *std::launder(reinterpret_cast<const T*>(&instance_storage_));
// std::destroy_at(std::launder(reinterpret_cast<T*>(&instance_storage_)));
return reinterpret_cast<InstanceType*>(&instance_storage_);
leveldb扩展3:aligned_storage
aligned_storage
// 长度 默认对齐方式
// instance_storage_表示 长度至少为sizeof(InstanceType)并且以InstanceType大小对齐
typename std::aligned_storage<sizeof(InstanceType),
alignof(InstanceType)>::type instance_storage_;
创建给定类型对象大小满足对齐要求的未初始化内存块,在一个内存对齐的缓冲区上创建对象。
class A
{
int a;
char d;
};
// C++11后可以这样操作
void align_cpp11_after()
{
static std::aligned_storage<sizeof(A),
alignof(A)>::type data;
A *attr = new (&data) A;
}
// C++11之前
void align_cpp11_before()
{
static char data[sizeof(void *) + sizeof(A)];
const uintptr_t kAlign = sizeof(void *) - 1;
char *align_ptr =
reinterpret_cast<char *>(reinterpret_cast<uintptr_t>(data + kAlign) & ~kAlign);
A *attr = new (align_ptr) A;
}
alignas
指定对齐要求
// 每个 struct_float 类型对象都将被对齐到 alignof(float) 边界
// (通常为 4):
struct alignas(alignof(float)) struct_float
{
// 定义在此
};
alignof
查询对齐要求
cout << alignof(struct_float) << endl; // 4
5.Random.h
生成随机数的类。
// 随机数
uint32_t seed_;
// 初始化变量
explicit Random(uint32_t s);
// 计算并返回下一个随机数
uint32_t Next();
// 生成一个在0到n-1的随机数
uint32_t Uniform(int n);
//true表示随机数在0到n-1内
bool OneIn(int n);
uint32_t Skewed(int max_log);