me's People
me's Issues
TIME_WAIT和CLOSE_WAIT状态
Reference
Wait Morphing
对于以下用条件变量来实现事件等待器的代码:
// waiter
pthread_mutex_lock(&mutex);
while (!flag) {
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
// signaler
pthread_mutex_lock(&mutex);
flag = 1;
pthread_cond_signal(&cond); // [1]
pthread_mutex_unlock(&mutex); // [2]
在代码[1]和[2]处先调用signal函数,接着调用unlock释放锁,但是与先unlock再signal相比,这样可能会造成额外的两次上下文切换开销。考虑以下的执行情况:
- signaler线程调用signal函数,唤醒waiter线程
- waiter线程被唤醒,尝试获得锁,但此时锁仍然被signaler线程持有,waiter线程进入睡眠
- signaler线程得到调度,释放了锁
- waiter线程得到锁
对于以上这种情况,waiter线程可能会发生多余的一次唤醒操作,造成额外的性能开销。Linux内核为了解决这一问题存在专门的优化技术:Wait Morphing。
Wait Morphing优化
内核知道在上述执行情况的第2步,并不能使得被唤醒的线程获得锁,所以把等待在该condvar下的所有等待线程,转移到mutex的等待线程队列中,而不会去真正调度该线程,从而免去多余的上下文切换开销。
参考资料
For语句代码生成模式
RednaxelaFX在文章“对C语义的for循环的基本代码生成模式”中总结了for循环代码生成的三种模式。对于以下for循环语句:
for (loop-initialize; loop-test; loop-increment)
loop-body
文中给出了以下三种不同的代码生成模式:
- 循环条件放前面,循环末尾用无条件跳转回到开头
这3种代码生成模式中,最优的生成模式是方法3,具体参见论文The Best Simple Code Generation Technique for WHILE, FOR, and DO Loops
Java Autoboxing and unboxing
False sharing
What
现代处理器在读取内存数据时,数据同时被读取到L1和L2中,通常L1中cache line大小是64字节,每个处理器核心都有各自独立的L1和L2缓存(cache)。当多线程程序访问互相独立的变量,如果这个变量被加载到同一缓存行,并且一个线程对变量进行写操作,另一个线程对变量进行读或写操作,不同核心(core)之间的cache需要同步,这将导致性能的严重退化。这种现象称为False Sharing。
Example
struct foo {
int x;
int y;
};
foo f;
// 下面两个函数在不同线程中运行
int sum_add() {
int s = 0,i;
for (i = 0; i < 1000000; ++i)
s += f.x;
return s;
}
void inc_b(void) {
int i;
for (i = 0; i < 1000000; ++i)
++f.y;
}
变量f占用小于64字节的缓存行,sum_add和inc_b线程会将其加载至各自的私有缓存,虽然每个线程访问结构体的不同域(sum_add读f.x,inc_b写f.y),由于缓存同步仍然会对性能造成退化。
How
把每个项凑齐缓存行的长度,实现隔离。
Reference
Incomplete Type
LevelDB Overview
设计思路与整体架构
LevelDB 是一种以 LSM-tree[1] 为底层数据结构的写优化的键值数据库。
在具体的实现中,LevelDB 主要包含 Memtable、WAL、分层的 SST 文件、用于版本控制的 Manifest 和 CURRENT 文件。
- Memtable 内存数据结构,支持并发读写的跳表[2] 实现,新写入的 kv 会先写到 Memtable 中
- Immutable Memtable 当 Memtable 写入数据量达到阈值时,会生成一个只读的 Memtable 并重新生成一个 Memtable,后台线程会进行 minor compaction 操作,将 immutable memtable 写入 Level 0 的 SST 文件中
- WAL 预写式日志,在写入 Memtable 之前 kv 数据会先写入 WAL 中,用于机器崩溃重启时的数据恢复
- SST 磁盘文件,LevelDB 的每一层由多个 SST 文件组成,每一个 SST 文件是一个只读且有序的 kv 序列。Level 0 中的 SST 文件由 Immutable memtable dump 得到,Level N( N >= 1) 层中的 SST 文件由 Level N-1 层中的 SST 文件和本层文件 merge 得到。注意:Level 0 中 SST 文件中的 keys 是可能存在重叠的,而 Level N(N >= 1) 层中各个 SST 文件的 keys 是不重叠的
- Manifest Manifest 文件中记录了各个层中所有 SST 文件的元数据。每个 SST 文件的元数据主要包括最大最小 key、文件大小等信息
- CURRENT LevelDB 支持 MVCC,所以同时可能存在多个版本的 Manifest 文件,CURRENT 文件中的内容指向当前的 manifest 文件
写流程
LevelDB 的写入过程首先将 kv 写入 WAL 日志中,再将 kv 写入到 Memtable 中即可。详细的流程在之后的文章中会进行分析。
// 写入 WAL
log_->AddRecord(WriteBatchInternal::Contents(updates));
// 写 Memtable
WriteBatchInternal::InsertInto(updates, mem_);
读流程
LevelDB 的读取流程,首先从 Memtable 中查找 key,如果没有找到则从 Immutable Memtable 中查找 key,如果仍然没有找到则需要从各层中的 SST 文件中进行查找。
if (mem->Get(lkey, value, &s)) { // 首先从 Memtable 中查找
// Done
} else if (imm != NULL && imm->Get(lkey, value, &s)) { // 然后从 Immutable Memtable 中查找
// Done
} else { // 最后依次从各层 SST 文件中查找
s = current->Get(options, lkey, value, &stats);
have_stat_update = true;
}
参考文献
Spurious wakeup
What
在使用条件变量时,常常使用如下编程模式
pthread_mutex_lock(&mutex);
while (condition is false)
pthread_cond_wait(&cond,&mutex);
上面代码的 while 循环是必不可少的,因为当线程从 pthread_cond_wait 返回时并不能保证条件真的成立。当线程从 pthread_cond_wait 返回时条件仍为假的现象称为虚假唤醒(Supurious wakup)。
Why
ptherad_cond_wait 在 Linux/x64 的内部实现中使用 futex 系统调用挂起当前线程,而 futex 系统调用是慢速系统调用(slow system call),当该调用被中断时将返回 EINTR,所以慢速系统调用的重启功能被关闭后往往需要手动进行重启。
// 手动重启慢速系统调用
for (;;) {
int r = syscall();
if (r < 0 && r == EINTR) {
continue;
} else {
break;
}
}
但是 futex 不能这样用,因为从 futex 结束到重启这个时间窗口内有可能其他线程调用了 pthread_cond_signal/pthread_cond_broadcast ,如果此时 futex 再次被调用将错过条件变量的变化而可能永久等待下去。
所以,正确的用法正是如上所述的编程模式,当出现虚假唤醒时,再次通过 while 循环进行条件判断并继续调用 pthread_cond_wait 等待条件成立。
The C++ Most Vexing Parse
优雅的关闭Golang Channel
从汇编角度看C++ lambda表达式
C++11引入了lambda表达式,我们从汇编角度来看lambda表达式底层的实现机制。
- No Capturing
int main() {
auto lambda = [](int a) { return a + 3; }
lambda(2);
}
以-g选项对代码进行编译,得到以下汇编代码
main: # @main
push rbp
mov rbp, rsp
sub rsp, 16
mov dword ptr [rbp - 4], 0
lea rdi, [rbp - 8] ; [1]
mov esi, 2
call main::$_0::operator()(int) const
xor esi, esi
mov dword ptr [rbp - 12], eax # 4-byte Spill
mov eax, esi
add rsp, 16
pop rbp
ret
main::$_0::operator()(int) const: # @"main::$_0::operator()(int) const"
push rbp
mov rbp, rsp
mov qword ptr [rbp - 8], rdi
mov dword ptr [rbp - 12], esi
mov esi, dword ptr [rbp - 12]
add esi, 3
mov eax, esi
pop rbp
ret
C++编译器将一个lambda表达式编译成了一个函数,从[1]处开始的3条汇编指令对应C++代码中的lambda(2)
函数调用。虽然lambda只有一个形参a
,但是在call指令之前,除了将实参2存入寄存器esi
,还将main函数栈帧中 ebp - 8
对应的地址存入寄存器edi
,这个隐含参数用于对捕获参数的访问。
Dangling else
Hello,World
Basic Paxos
Double Checked Locking
What
双检查锁是多线程环境下延迟初始化单例的方法。
How
考虑下面代码:
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper(); // [1]
}
return helper;
}
// other functions and members...
}
上面的代码其实是不安全的,原因在于 [1] 处的代码并不是一个原子操作,它包含下面三个步骤:
- 分配Helper对象占用的内存
- 调用Helper类的构造函数
- 将对象实例赋值给helper引用
如果发生乱序执行,1->3->2,可能在另外一个线程中看到完成步骤1->3的对象引用,但这是一个尚未构造完成的对象引用,这是典型的多线程不安全发布行为。
在JDK5.0以上版本中,将变量helper用volatile进行修饰,则可以避免上述的不安全行为:
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper(); // [1]
}
return helper;
}
// other functions and members...
}
这是因为JDK5.0以上版本增强了volatile的语义:在代码层面上happens before写volatile变量的行为在程序执行时必须happens before写操作(也就是禁止了指令重排序优化)。
Reference
The "Double-Checked Locking is Broken" Declaration
double checkd locking
Chandy-Lamport Distributed Snapshots Algorithm
一个分布式的快照可以理解为在某个瞬时时刻或时间静止时,整个系统中各个节点和节点间 channel 的状态集合。难点在于系统运行过程中是无法静止时间的。Chandy-Lamport Snapshot 算法以 marker 消息做为切割全局视角下状态的切割点。对于每个节点而言,第一次收到 marker 消息时自身的状态做为全局状态的一部分。对于每个节点 p 的 input channel c 而言,如果 p 首次收到 marker 消息,c 的状态是 empty(PS: p 收到的 marker 消息之前的消息已经作用在了 p 的本地状态中);如果 p 非首次收到 marker 消息,那么肯定存在一条环 p -> ... -> ... -> p,c 是经过这条环与 p 直接相连的 channel,此时从 p 记录状态开始到从 c 收到 marker 消息之间 c 中的消息序列是由于系统运动处于 marker 时间点之前未被节点 p 消费的消息,这些消息序列也属于快照的一部分,与所有节点的本地状态共同构成全局的分布式快照。
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.