高性能编程-无锁化编程总结

什么是无锁化编程
无锁化编程(Lock-Free):不使用锁的情况下实现多线程之间对变量进行同步和访问的一种程序设计实现方法,Lock-Free的程序不包括锁机制,但不包括锁机制的程序不一定是lock-free的。

无锁编程有如下几点优势:
加锁,等待锁涉及系统调用,影响性能。无锁编程没有这部分的性能损耗,不会产生死锁。

无锁化编程实现方式:
1、RMW原子操作:RMW(read-modify-write)原子操作,是指把“读-改-写”三步指令合并到一个原子操作里。
2、CAS:CAS(compare-and-swap)是一种典型的RMW原子操作,它将以下操作封装在一个原子操作里:
(1)读变量p。
(2)对比
p与变量的old值。
(3)如果p与old值不相同,不做任何操作。
(4)如果
p与old值相同,将另一变量new值赋值给p。
3、ABA问题:CAS算法有一个缺陷,就是会产生ABA问题。ABA问题是指,在执行旧值缓存到本地局部变量和CAS操作之间,线程被切换走,旧值被修改了两次以上,恰好与原旧值相同。cas函数误认为数据没有发生变化,而实际上,数据已经与原来不一样了。
ABA问题,通常通过添加操作引用计数来解决。cas中,除了对比
p与变量old外,还需要对比操作计数是否改变。如果值和操作计数都没有改变,才算cas成功,才可以给p赋于新值。
4、内存屏障(Memory Barrier):首先要了解内存乱序:程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致,这就是内存乱序访问。内存乱序访问行为出现的理由是为了提升程序运行时的性能。内存屏障(Memory Barrier),就是为了解决内存乱序的问题。
5、C++11为我们提供了一组封装好的CAS接口:
_Bool atomic_compare_exchange_strong( volatile A
obj, C* expected, C desired );
_Bool atomic_compare_exchange_weak( volatile A obj, C expected, C desired );
这一组CAS接口,比较obj与expected是否相等,如果相等,则将desired赋值给obj,并返回true;否则返回false。也就是“原子地”执行以下逻辑。

6、无锁编程涉及到的技术要点
(1)原子操作(atomic operations)
(2)内存屏障(memory barriers)
(3)内存顺序(memory order)
(4)指令序列一致性(sequential consistency)
(5)ABA现象处理
加锁虽然数据安全了,但在加锁的区域内,没有并发了,只能串行访问(只要一个线程访问,其他线程必须阻塞),程序性能大打折扣。
在多线程环境下,也可以不加锁(无锁化编程),其通过乐观锁,自旋锁达到数据安全的访问。
加锁和无锁是相互补充,无法完全将另一种取代,在批处理的情况下,无锁化优化空间比加锁要小。

7、死锁的发生必须具备以下四个必要条件:
(1)互斥条件
(2)请求和保持条件
(3)不可剥夺条件
(4)环路等待条件(循环等待条件)

8、无锁化编程的常见方法:
(1)Atomic原子操作,针对计数器,可以使用原子加,比如Atomiclnteger_sync_fetch_and_add。
(2)CAS(Compare and Swap):如果a等于b,那么把a赋值成c。
用一个循环看观察变量,如果变量在我访问变量这段时间内没有被别人修改,我就放心去修改变量。比如:cpu指令级别,CMPXCHG指令,比较看内存的值有没有被修改,如果发现值在这段时间没有被修改,就可以放心去修改这个值。会有一个问题,别的线程该了这个值之后又改回来了,这时候,通过这个方式就发现不了,这时候需要加入版本控制。
(3)Ring Buffer环形队列:使用数组实现队列构成一个环,环形队列,适合一个线程负责读一个线程负责写的情况,不需要加锁 -RCU(Read-Copy-Update) ,新旧副本切换机制,对于旧副本可以采用延迟释放的做法。

9、原子操作的顺序性问题:
(1)原子操作不仅要保证操作的原子性,还要考虑在不同的语言和内存模型下如何保证操作的顺序性,编译时和运行时的指令重排,内存顺序冲突(Memory order violation) 等问题。
(2)原子性确保指令执行期间不被打断,要么全部执行,要么根本不执行。
(3)顺序性确保即使两条或多条指令出现在独立的执行线程中或者独立的处理器上时,保持它们本该执行的顺序。

10、原子操作顺序性问题解决方法:
(1)加锁,串行执行,但效率不高,优先级反转等问题。
(2)原子操作,C++11内存模型。
(3)synchronized-with机制 假设X是一个原子变量。如果线程A写了X,线程B读了x,那么线程A、B间存在synchronized-with关系,它能保证对X的读和写是互斥的。
(4)happens-before机制。
(5)内存屏障。

11、无锁化编程之CAS操作:
CAS(Compare and Swap,即比较并替换),wikipedia中对于CAS的定义为:
(1)是一种多线程中的原子操作。
(2)类似于乐观锁,只有当读取的值和预期值相等,就认为没有其他线程修改过当前的共享资源,但如果有线程修改了共享资源再改回来,乐观锁就还是认为没被修改了)时才进行赋新值。
(3)返回结果必须表明是否成功
(4)CAS操作包含三个操作数:内存地址(V)、预期原值(A)、新值(B)。如果内存地址的值与预期原值相同,那么处理器会自动将内存的值更新为新值。否则,处理器不做任何操作。无论哪种情况,处理器会在CAS指令之前返回该地址的值。CAS有效地说明了"我认为地址V应该包含值A;如果包含该值,则将B放到这个地址;否则,不要更新该地址,只告诉我这个地址现在的值即可"。
(5)适用场景: CAS适合简单对象的操作,比如布尔值、整型值等; CAS适合冲突较少的情况,如果太多线程在同时自旋,那么长时间循环会导致CPU开销很大。

12、CAS面临的问题:
(1)ABA问题:如果内存地址V初次读取的值是A,在CAS等待期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过(手提箱例子,把手提箱的数据读取,然后再把手提箱放回去)。
ABA问题以及解决:使用带版本号的原子引用AtomicStampedRefence,比如:加1个引用计数。
(2)高并发下,反复更新某个变量,却不成功,一直自旋(不会阻塞不会睡眠),导致CPU负载过高。
(3)只保证对一个变量进行原子操作,多个变量无能为力,只能加锁了。

13、CAS实现无锁队列
(1)判断队列是否为空。
(2)常规队列:队头指针和队尾都为NULL。
(3)无锁队列:队头指针和队尾都指向一个节点,这个结点的next==NULL。

14、无锁编程之环形缓冲区(ring buffer):
(1)如果只有一个生产者和一个消费者,那么就可以做到免锁访问环形缓冲区(Ring Buffer)生产者将数据放入数组的尾端,而消费者从数组的另一端(头部)移走数据,当达到数组的尾部时,生产者绕回到数组的头部。
(2)写入索引(或指针,rear)只允许生产者访问并修改,只要写入者在更新索引之前将新的值保存到缓冲区中,则读者将始终看到一致的数据。
(3)同理,读取索引(front)也只允许消费者访问并修改。
(4)环形缓冲区,如果生产者和消费者的速度不一致,速度差*足够长的时间 > 环形缓冲区长度,不会出问题吗?满了肯定不能再生产了;同理,空了肯定不能再读了

15、只有少数数据结构可以支持实现无锁编程,比如队列、栈、链表、词典等。
通过对锁之间的数据进行批处理,可以极大的提高系统的性能,而使用原子操作,则无法实现批处理上的改进。
/// 加锁与批处理
lock();
/// 当循环越大,与原子操作相比,加锁程序的系统性能越强
for(k=0;k<100;k++)
{
k++;
}
unlock();

/// CAS原子操作,没办法优化批处理
for(k=0;k<100;k++)
InterlockedIncrement(k);

16、c++11中的6种memory_order解释说明:
(1)memory_order_relaxed:不对执行顺序做任何保证。
(2)memory_order_acquire:本线程中,所有后续的读操作必须在本条原子操作完成后执行。
(3)memory_order_release:本线程中,所有之前的写操作完成后才能执行本条原子操作。
(4)memory_order_acq_rel:同时包含memory_order_acquire和memory_order_release标记。
(5)memory_order_consume:本线程中,所有后续的有关本原子类型的操作,必须在本条原子完成之后执行。
(6)memory_order_seq_cst:全部存取都按顺序执行。

通常情况下,我们可以把atomic成员函数可使用的memory_order值分为以下3组:

  1. 原子存储操作(store)可以使用memorey_order_relaxed、memory_order_release、memory_order_seq_cst。
  2. 原子读取操作(load)可以使用memorey_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_seq_cst。
  3. RMW操作(read-modify-write),即一些需要同时读写的操作,比如之前提过的atomic_flag类型的test_and_set()操作。又比如atomic类模板的atomic_compare_exchange()操作等都是需要同时读写的。RMW操作可以使用memorey_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_release、memory_order_acq_rel、memory_order_seq_cst。
  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值