lock-free vs wait-free

文章目录

  • 为什么需要lock-free
  • 从底层去看lock-free
    • atomic指令的层级
  • atomic操作
  • wait-free

为什么需要lock-free

什么是lock-free?lock-free通常用于结构体中,此结构体经常被多个线程访问,假如处于blocking中,一个线程访问这个结构体那么其他的线程访问结构体都会被blocking住(ie mutex),假如正在访问这个结构体的线程(已经获得锁)因为某一些原因crash了,那么其他待访问这个结构的线程就被永远block住…这个时候我们就需要lock-free,而lock-free意思就是一个线程访问结构体,其他的结构也可以访问且不会被block住,也不会造成race,,,

从底层去看lock-free

lock-free是一个非常底层的东西,lock-free编程需要atomic指令这个指令是cpu提供的,cpu原生的指令有非常多都是atomic的,这些指令集分为2大类,分别是store-and-loadread-modify-write

store-and-load
这些指令用于读,写数据到内存中,许多的cpu架构都保证这些操作是原子的,比如mov

read-modify-write
有一些操作需要多个指令比如要对内存中的一个数据进行+1,这至少需要三个原子操作指令,虽然说这3个原子操作是原子的,但是加一起就不是原子的了read-modify-write就是file the gap,在一个原子操作下去执行多个操作,比如test-and-set :将1写入到内存的地址中,然后返回旧的值,fetch-and-add:在内存中的值加上一个数字,然后返回老的值

atomic指令的层级

所有的指令都属于硬件,我们通过指令直接与cpu对话,这样工作太费劲,不同的cpu架构有不同的指令集,所以在此之上非常多的操作系统提供了他们的原子操作,我们就直接叫这些东西叫做atomic operations,但是我们如果用操作系统提供的原子操作就不能跨平台,因为linux的原子操作是一个样子的,windows的原子操作是一个样子的,c++的原子操作是一个样子的,最好的方法还是用编程语言提供的跨平台的原子操作

操作系统本质就是不断地封装,底层指令集因为不同cpu不同,指令集不同,此时我们将不同的指令集抽象成一个操作系统的syscall,但是不同的操作系统拥有不同的syscall,那么我们再抽象成语言的跨平台包

原子操作

-------------------------------------
|      programming laguage atomics  |
|    -----------------------------  |
|    |   operating system atomics|  |
|    | ------------------------- |  |
|    |  | atomic instructions |  |  |
|    | --------------------------   | 
|------------------------------------

atomic操作

假设我们要对一个数字进行++并且打印传统的操作是用mutex

std::mutex mu;
x = 0;

reader_thread(){
	mu.lock();
	print(x);
	mu.unlock();
}

writer_thread(){
	mutex.lock();
	x++
	mutex.unlock();
}

以上是传统做法,而原子操作则不一样,原子操作中所有的线程执行都是无lock的如下假设load()fetch_and_add()都是对应底层硬件的原子操作指令

x = 0
reader_thread(){
	print(load(x))
}

writer_thread(){
	fetch_and_add(x, 1)
}

在现实的lock-free编程中我们主要用到CAS loop来进行lock-free编程**CAS全称(compare-and-swap loop)**这个函数一般原型如下

bool compare_and_swap(shared_data, expected_value, new_value);

这个函数的意思是对shared_data的值expected_value替换成new_value,当expected_value没有改变就返回false(因为shared_data的值可能不是expected_value,而被其他的线程改了),如下

x = 0

reader_thread(){
	print(load(x))
}

writer_thread(){
	temp = load(x);
	while(!compare_and_swap(x, temp, temp+1)){  //temo没有改变就返回false,改变了就返回true
		//retry??
	}
}

上面也叫自旋锁

wait-free

所有的原子操作可以分为2大类,分别是lock-freewait-free

lock-free可以允许线程继续做他自己的事情(在快被阻塞之前)
wait-free是lock-free的子集,所有线程都可以在有限的步骤中完成其工作,而不管其他的线程执行速度或者负载水平如何,上面的 fetch-and-add() 就是一个wait-free的示例,没有loop,没有重试


转自here

你可能感兴趣的:(c/c++,c++)