《c++并发编程实战解析》 无锁数据结构 doubly-buffered-data

多线程环境设计数据结构相比单线程,需要额外注意的是利用多线程提升并发度同时保持数据结构不变性,即满足如下两个原则:

1、正确性,保证多线程并发访问没有 data race

2、性能,保护最小的数据,提供最大的性能

《c++并发编程实战》提供了一种【无锁数据结构】,注意这里无锁的含义不是真正无锁,而是利用数据结构特性保证运行时并发抢锁的线程数量最小,达到一种常态下访问数据结构不被锁阻塞的状态

使用场景:

1、读远多于写

2、数据小,一般最大为几K字节的元数据

3、数据可以应用到一致性状态机,即byte级别判断为相等的两份数据,执行同种操作后仍保持一致

和传统读写锁的区别和优势:single unix的读写锁要求有线程在获取写锁阻塞时,后续读锁阻塞,从而防止写锁被饿死,这样造成了一个后果,若有写请求到来,则写操作完成前,无法处理新的读请求,从而造成系统【颠簸】,在有写请求的时候对外表现为性能下降,读延迟增加。DoublyBufferData将数据保存两份,分成前端和后端,读请求读前端,写请求到来写后端,用c++11  memory_order语义保证写请求完成后新的读请求可以立刻读取最新的结果同时无data race,是一种典型的【空间换时间】模式

数据结构图示:

原理描述:

1、DoublyBufferedData中包含foreground和background两份数据,index值为0或1,指示读请求应该访问的数据,如index为0表示此时应该读取data0,data0是只读的,因此多线程访问不需要并发保护,这里是无锁的

2、当有线程请求修改,首先根据 !index 获取background并做修改,background没有读者,因此这里可以放心修改,修改完成后,data0是旧数据,data1是新数据,使用memory_order_release语义修改index = !index,这样后续使用memory_order_acquire语义访问index的线程可以看到修改(inter-thread的sychronization-with关系)

3、对于此前正在访问data0的线程,通过遍历wrappers可以得到这些线程各自对应的wrapper,进而等待所有这些线程访问结束

4、到这里,所有之前读data0的请求都结束了,所有后续的读请求都请求到data1,此时可以放心的修改data0,从而完成一次修改操作

代码:

1、DoublyBufferedDataWrapperBase:

提供线程私有存储的ABC类,T是数据类型,TLS用于线程私有存储,可用于保存上下文,没有特殊要求不需要使用TLS

template <typename T, typename TLS>
class DoublyBufferedDataWrapperBase {
public:
    TLS& user_tls() { return _user_tls; }
protected:
    TLS _user_tls;
};

2、Wrapper:

每个线程使用一个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值