交易系统开发技能及面试之无锁编程(Lock-free)

Posted BBinChina

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了交易系统开发技能及面试之无锁编程(Lock-free)相关的知识,希望对你有一定的参考价值。

概要

这一章内容主要讲解无锁编程相关技能,在生产中,为了解决线程间资源共享问题,最常用的方式就是加锁了,这在上一章并发编程也讲到过很多。关于无锁的时候,也写过一篇无锁队列的实现,而今天这篇文章主要是讲解无锁编程所需要了解的基础知识,比如atomics,内存模型,内存排序等等。

Q1 什么是atomic?

std::atomic<T> x;

以上代码采用标准库的atomic类型声明了一个T类型的原子变量x。在计算机指令执行过程中,一个变量的改变包含 加载(load),更新(update)和 存储(store)3个步骤,原子变量的改变意味着这3个步骤的中间态不会被其他线程所见,避免了普通变量指令乱序带来的非意料操作。

Q2 alignas 关键字

cpu(32位)在一个时钟周期可以读取4个连续的内存单元,即4字节。使用字节对齐将会提高系统的性能,比如将一个int(4个字节)放在奇数内存位置上时,需要两个时钟周期才能读取完,而如果数据用4字节对齐之后,只需要一次就可以读取出来

以下为alignas的用法

alignas(32) long long a = 0;

#define XX 1
struct alignas(XX) MyStruct ;

template <size_t YY = 1>
struct alignas(YY) MyStruct ;

static const unsigned zz = 1;
struct alignas(ZZ) MyStruct ;

//char用int的方式对齐
alignas(int) char c;

alignas只能改大的值,而不能改小,需改小时可采用 #pragma pack 或者 _Pragma(微软暂不支持)

_Pragma("pack(1)")
struct MyStruct 
	char a;
	int b;
	short c;
	long long d;
	char e;
;
_Pragma("pack()")

Q3 C++的内存模型 relaxed ordering, release-acquire ordering, sequentially consistent ordering

关于c++的内存模型,分为三种: relaxed ordering(松弛次序)、acquire-release ordering(获取-释放次序)、sequentially consistent ordering(顺序一致次序),C++11 提供了6种操作(memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_reelase, memory_order_acq_rel, memory_order_seq_cst)

relaxed ordering(松弛次序),使用memory_order_relaxed标记原子变量的load和store操作,不强制其在并发环境中的内存访问顺序,只保证当前操作的原子性。

//定义全局原子变量,让其在thread1 thread2中访问
std::atomic<int> x = 0;
std::atomic<int> y =0;

thread-1

r1 = y.load(memory_order_relaxed);//A
x.store(r1, memory_order_relaxed);//B

thread-2

r2 = x.load(memory_order_relaxed);//C
y.store(1, memory_order_relaxed);//D

以上在cpu执行指令乱序的情况下有可能出现 D-》A-》B-》C的顺序,即r1 == r2 == 1.

如果某个操作值要求是原子操作,不需要其他同步保障的话,可以使用Relaxed ordering模型,比如做计数器的数值

acquire-release ordering(获取-释放次序),release与acquire操作是配对出现在希望同步语义的线程间,比如原子变量在A线程用memory_order_release标记其store操作,而在B线程用memory_order_acquire标记其load操作,即在Store之前的所有读写操作不允许被移动到Store之后,Load之后的所有读写操作,不允许被移动到Load前面。避免了cpu指令的乱序,保证了Store跟Load之间的读写次序。其他线程的读写次序不受影响,该咋乱咋乱。

#include <thread>
#include <atomic>
#include <cassert>
#include <string>

std::atomic<bool> readyfalse;
int data = 0;
void producer() 
	data = 100;//A
	ready.store(true, std::memory_order_release);//B


void consumer() 
	while(!ready.load(std::memory_order_acquire))
		;
	assert(data == 100);


int main() 

	std::thread t1(producer);
	std::thread t2(consumer);

	t1.join();
	t2.join();

	return 0;

A指令始终在B指令之前执行,而D指令一定在C指令之后执行,如果已经退出了while,所以data == 100,那么assert不会捕获

sequentially consistent ordering(顺序一致次序),使用memory_order_seq_cst标记后的原子变量,线程间指令重排的限制与在顺序性代码中进行指令重排的限制时一致的。

以上是关于交易系统开发技能及面试之无锁编程(Lock-free)的主要内容,如果未能解决你的问题,请参考以下文章

交易系统开发技能及面试之低延迟编程技术

交易系统开发技能及面试之低延迟编程技术

交易系统开发技能之并发编程面试题

并发编程(学习笔记-共享模型之无锁)-part5

交易系统开发技能及面试之TechCoding

交易系统开发技能及面试之TechCoding