1.编程「锁」事|详解乐观锁 CAS 的码解技术原理
2.ReentrantLock 源码解析 | 京东云技术团队
3.C#中使用CAS实现无锁算法
4.ListenableFuture源码解析
5.画面设置cas是什么意思?
6.从HotSpot源码,深度解读 park 和 unpark
编程「锁」事|详解乐观锁 CAS 的码解技术原理
本文深入探讨乐观锁的核心实现方式——CAS(Compare And Swap)技术原理。CAS是码解一种在多线程环境下实现同步功能的机制,相较于悲观锁的码解加锁操作,CAS允许在不使用锁的码解情况下实现多线程间的变量同步。Java的码解瑞美lis源码并发包中的原子类正是利用CAS实现乐观锁。
CAS操作包含三个操作数:需要更新的码解内存值V、进行比较的码解预期数值A和要写入的值B。其逻辑是码解将内存值V与预期值A进行比较,当且仅当V值等于A时,码解通过原子方式用新值B更新V值(“比较+更新”整体是码解一个原子操作),否则不执行任何操作。码解一般情况下,码解更新操作会不断重试直至成功。码解
以Java.util.concurrent.atomic并发包下的码解AtomicInteger原子整型类为例,分析其CAS底层实现机制。方法`atomicData.incrementAndGet()`内部通过Unsafe类实现。Unsafe类是底层硬件CPU指令复制工具类,关键在于compareAndSet()方法的返回结果。
`unsafe.compareAndSwapInt(this, valueOffset, expect, update)`
此方法中,参数`this`是Unsafe对象本身,用于获取value的内存偏移地址。`valueOffset`是value变量的内存偏移地址,`expect`是期望更新的值,`update`是要更新的最新值。如果原子变量中的value值等于`expect`,则使用`update`值更新该值并返回true,否则返回false。
至于`valueOffset`的来源,这里提到value实际上是volatile关键字修饰的变量,以保证在多线程环境下的内存可见性。
CAS的底层是Unsafe类。如何通过`Unsafe.getUnsafe()`方法获得Unsafe类的实例?这是因为AtomicInteger类在rt.jar包下,因此通过Bootstrap根类加载器加载。Unsafe类的具体实现可以在hotspot源码中找到,而unsafe.cpp中的C++代码不在本文详细分析范围内。对CAS实现感兴趣的读者可以自行查阅。
CAS底层的Unsafe类在多处理器上运行时,为cmpxchg指令添加lock前缀(lock cmpxchg),在单处理器上则无需此步骤(单处理器自身维护单处理器内的顺序一致性)。这一机制确保了CAS操作的原子性。
最后,同学们会发现CAS的操作与原子性密切相关。CPU如何实现原子性操作是一个深入的话题,有机会可以继续探索。欢迎在评论区讨论,python编程源码剖析避免出现BUG!点赞转发不脱发!
ReentrantLock 源码解析 | 京东云技术团队
并发指同一时间内进行了多个线程。并发问题是多个线程对同一资源进行操作时产生的问题。通过加锁可以解决并发问题,ReentrantLock 是锁的一种。
1 ReentrantLock
1.1 定义
ReentrantLock 是 Lock 接口的实现类,可以手动的对某一段进行加锁。ReentrantLock 可重入锁,具有可重入性,并且支持可中断锁。其内部对锁的控制有两种实现,一种为公平锁,另一种为非公平锁.
1.2 实现原理
ReentrantLock 的实现原理为 volatile+CAS。想要说明 volatile 和 CAS 首先要说明 JMM。
1.2.1 JMM
JMM (java 内存模型 Java Memory Model 简称 JMM) 本身是一个抽象的概念,并不在内存中真实存在的,它描述的是一组规范或者规则,通过这组规范定义了程序中各个变量的访问方式.
由于 JMM 运行的程序的实体是线程。而每个线程创建时 JMM 都会为其创建一个自己的工作内存 (栈空间), 工作内存是每个线程的私有数据区域。而 java 内存模型中规定所有的变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问,但线程的变量的操作 (读取赋值等) 必须在自己的工作内存中去进行,首先要将变量从主存拷贝到自己的工作内存中,然后对变量进行操作,操作完成后再将变量操作完后的新值写回主内存,不能直接操作主内存的变量,各个线程的工作内存中存储着主内存的变量拷贝的副本,因不同的线程间无法访问对方的工作内存,线程间的通信必须在主内存来完成。
如图所示:线程 A 对变量 A 的操作,只能是从主内存中拷贝到线程中,再写回到主内存中。
1.2.2 volatile
volatile 是 JAVA 的关键字用于修饰变量,是 java 虚拟机的轻量同步机制,volatile 不能保证原子性。 作用:
作用:CAS 会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读 - 改 - 写操作。
1.2.4 AQSAQS 的全称是 AbstractQueuedSynchronizer(抽象的队列式的同步器),AQS 定义了一套多线程访问共享资源的同步器框架。
AQS 主要包含两部分内容:共享资源和等待队列。AQS 底层已经对这两部分内容提供了很多方法。
2 源码解析
ReentrantLock 在包 java.util.concurrent.locks 下,实现 Lock 接口。srpm源码包管理
2.1 lock 方法
lock 分为公平锁和非公平锁。
公平锁:
非公平锁:上来先尝试将 state 从 0 修改为 1,如果成功,代表获取锁资源。如果没有成功,调用 acquire。state 是 AQS 中的一个由 volatile 修饰的 int 类型变量,多个线程会通过 CAS 的方式修改 state,在并发情况下,只会有一个线程成功的修改 state。
2.2 acquire 方法
acquire 是一个业务方法,里面并没有实际的业务处理,都是在调用其他方法。
2.3 tryAcquire 方法
tryAcquire 分为公平和非公平两种。
公平:
非公平:
2.4 addWaiter 方法
在获取锁资源失败后,需要将当前线程封装为 Node 对象,并且插入到 AQS 队列的末尾。
2.5 acquireQueued 方法
2.6 unlock 方法
释放锁资源,将 state 减 1, 如果 state 减为 0 了,唤醒在队列中排队的 Node。
3 使用实例
3.1 公平锁
1. 代码:
2. 执行结果:
3. 小结:
公平锁可以保证每个线程获取锁的机会是相等的。
3.2 非公平锁
1. 代码:
2. 执行结果:
3. 小结:
非公平锁每个线程获取锁的机会是随机的。
3.3 忽略重复操作
1. 代码:
2. 执行结果:
3. 小结:
当线程持有锁时,不会重复执行,可以用来防止定时任务重复执行或者页面事件多次触发时不会重复触发。
3.4 超时不执行
1. 代码:
2. 执行结果:
3. 小结:
超时不执行可以防止由于资源处理不当长时间占用资源产生的死锁问题。
4 总结
并发是现在软件系统不可避免的问题,ReentrantLock 是可重入的独占锁,比起 synchronized 功能更加丰富,支持公平锁实现,支持中断响应以及限时等待等,是处理并发问题很好的解决方案。
C#中使用CAS实现无锁算法
CAS(Compare-and-Swap)操作是一种多线程并发编程中常用的原子操作,用于实现多线程间的同步和互斥访问。它通过比较内存地址处的值与期望的旧值是否相等来实现这一目标。若相等,则将新值写入该内存地址;否则不做任何操作。CAS 操作的原子性由硬件层面的CPU指令保证,通常通过 Interlocked 类在 C# 中实现。
在C#中,我们使用Interlocked类的CompareExchange方法来实现CAS操作。此方法接收三个参数:内存地址、期望的旧值和新值。如果内存地址处的值与期望的旧值相等,则将新值写入该内存地址并返回旧值;否则不执行任何操作。东莞市源码通过判断返回值与期望的旧值是否相等,我们可以得知CompareExchange操作是否成功。
在使用CAS实现无锁算法时,我们通常需要在更新数据后执行进一步的操作。结合while(true)循环,我们可以在每次尝试更新数据后检查是否成功。如果失败,则继续尝试,直到成功为止。
以下是一个简单的计数器示例,它使用CAS实现了一个线程安全的自增操作。在CLR底层源码中,我们也经常看到使用类似方法实现线程安全计数器的代码。同时,队列类也使用CAS实现线程安全的入队和出队操作,该操作更为复杂,需要不断检查是否有其他线程修改数据。
在复杂的无锁算法中,每一步操作都必须考虑是否被其他线程修改。每一步操作非原子,因此我们不仅依赖CAS操作,还必须确保在执行每个操作前检查数据是否被修改。类比薛定谔的猫,我们不知道数据状态直到尝试修改时才确定。
通过测试代码,我们可以观察到在一定数量的操作中,需要重试的次数。这个重试次数取决于队列中是否有数据可供操作,而在多线程环境下,每次操作的结果可能有所不同。
CAS是一种乐观锁机制,假设数据未被其他线程修改,若未修改则直接修改,若已修改则重新获取数据并再次尝试修改。在实现复杂的数据结构时,我们不仅依赖CAS操作,还需注意数据是否被其他线程修改,以及处理可能的分支情况。
ListenableFuture源码解析
ListenableFuture 是 spring 中对 JDK Future 接口的扩展,主要应用于解决在提交线程池的任务拿到 Future 后在 get 方法调用时会阻塞的问题。通过使用 ListenableFuture,可以向其注册回调函数(监听器),当任务完成时,触发回调。Promise 在 Netty 中也实现了类似的app外包要源码功能,用于处理类似 Future 的场景。
实现 ListenableFuture 的关键在于 FutureTask 的源码解析。FutureTask 是实现 Future 接口的基础类,ListenableFutureTask 在其基础上做了扩展。其主要功能是在任务提交后,当调用 get 方法时能够阻塞当前业务线程,直到任务完成时唤醒。
FutureTask 通过在内部实现一个轻量级的 Treiber stack 数据结构来管理等待任务完成的线程。这个数据结构由 WaitNode 节点组成,每个节点代表一个等待的线程。当业务线程调用 get 方法时,会将自己插入到 WaitNode 栈中,并且在插入的同时让当前线程进入等待状态。在任务执行完成后,会遍历 WaitNode 栈,唤醒等待的线程。
为了确保并发安全,FutureTask 使用 CAS(Compare and Swap)操作来管理 WaitNode 栈。每个新插入的节点都会使用 CAS 操作与栈顶节点进行比较,并在满足条件时更新栈顶。这一过程保证了插入操作的原子性,防止了并发条件下的数据混乱。同时,插入操作与栈顶节点的更新操作相互交织,确保了数据的一致性和完整性。
在 FutureTask 中,还利用了 LockSupport 类提供的 park 和 unpark 方法来实现线程的等待和唤醒。当线程插入到 WaitNode 栈中后,通过 park 方法将线程阻塞;任务执行完成后,通过 unpark 方法唤醒线程,完成等待与唤醒的流程。
综上所述,ListenableFuture 通过扩展 FutureTask 的功能,实现了任务执行与线程等待的高效管理。通过注册监听器并利用 CAS 操作与 LockSupport 方法,实现了在任务完成时通知回调,解决了异步任务执行时的线程阻塞问题,提高了程序的并发处理能力。
画面设置cas是什么意思?
CAS是Central Authentication Service的缩写,即集中式认证服务。它是一种用于Web应用程序的单点登录协议。CAS协议通过认证中心(服务器)来给多个服务提供认证服务,用户一次登录认证以后,便可以访问被授权的多个服务。CAS协议是一种开放源代码的协议,被广泛应用于大型企业和组织的身份认证系统中。
CAS需要先部署一个认证服务器和多个应用程序服务器,然后在这些服务器之间建立信任关系。用户首次登录时,应该重定向到认证服务器,输入用户名和密码进行认证,并且一旦通过认证,用户将被重定向回要访问的应用程序服务器。以后的每次访问都无需再次认证。认证服务器和应用程序服务器之间使用安全令牌和Session来保障安全性。
CAS的优点在于提供可靠的身份验证,减少了用户访问多个Web应用程序时的不必要的登录操作,避免了重复输入用户名和密码等问题。它广泛应用于大型企业和组织的身份认证系统中,例如教育机构、银行、保险公司、医院等。CAS的使用可以帮助企业或组织节省时间和成本,减少安全漏洞,提高用户体验并提高整个系统的安全性。
从HotSpot源码,深度解读 park 和 unpark
我最近建立了一个在线自习室(App:番茄ToDO)用于相互监督学习,感兴趣的小伙伴可以加入。自习室加入码:D5A7A
Java并发包下的类大多基于AQS(AbstractQueuedSynchronizer)框架实现,而AQS线程安全的实现依赖于两个关键类:Unsafe和LockSupport。
其中,Unsafe主要提供CAS操作(关于CAS,在文章《读懂AtomicInteger源码(多线程专题)》中讲解过),LockSupport主要提供park/unpark操作。实际上,park/unpark操作的最终调用还是基于Unsafe类,因此Unsafe类才是核心。
Unsafe类的实现是由native关键字说明的,这意味着这个方法是原生函数,是用C/C++语言实现的,并被编译成了DLL,由Java去调用。
park函数的作用是将当前调用线程阻塞,而unpark函数则是唤醒指定线程。
park是等待一个许可,unpark是为某线程提供一个许可。如果线程A调用park,除非另一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。每次调用一次park,需要有一个unpark来解锁。
并且,unpark可以先于park调用,但不管unpark先调用多少次,都只提供一个许可,不可叠加。只需要一次park来消费掉unpark带来的许可,再次调用会阻塞。
在Linux系统下,park和unpark是通过Posix线程库pthread中的mutex(互斥量)和condition(条件变量)来实现的。
简单来说,mutex和condition保护了一个叫_counter的信号量。当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。当_counter=0时线程阻塞,当_counter>0时直接设为0并返回。
每个Java线程都有一个Parker实例,Parker类的部分源码如下:
由源码可知,Parker类继承于PlatformParker,实际上是用Posix的mutex和condition来实现的。Parker类里的_counter字段,就是用来记录park和unpark是否需要阻塞的标识。
具体的执行逻辑已经用注释标记在代码中,简要来说,就是检查_counter是不是大于0,如果是,则把_counter设置为0,返回。如果等于零,继续执行,阻塞等待。
unpark直接设置_counter为1,再unlock mutex返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程。源码如下:
(如果不会下载JVM源码可以后台回复“jdk”,获得下载压缩包)
深入学习CAS底层原理
什么是CAS
CAS是Compare-And-Swap的缩写,意思为比较并交换。以AtomicInteger为例,其提供了compareAndSet(intexpect,intupdate)方法,expect为期望值(被修改的值在主内存中的期望值),update为修改后的值。compareAndSet方法返回值类型为布尔类型,修改成功则返回true,修改失败返回false。
举个compareAndSet方法的例子:
publicclassAtomticIntegerTest{ publicstaticvoidmain(String[]args){ AtomicIntegeratomicInteger=newAtomicInteger(0);booleanresult=atomicInteger.compareAndSet(0,1);System.out.println(result);System.out.println(atomicInteger.get());}}上面例子中,通过AtomicInteger(intinitialValue)构造方法指定了AtomicInteger类成员变量value的初始值为0:
publicclassAtomicIntegerextendsNumberimplementsjava.io.Serializable{ ......privatevolatileintvalue;/***CreatesanewAtomicIntegerwiththegiveninitialvalue.**@paraminitialValuetheinitialvalue*/publicAtomicInteger(intinitialValue){ value=initialValue;}......}接着执行compareAndSet方法,main线程从主内存中拷贝了value的副本到工作线程,值为0,并将这个值修改为1。如果此时主内存中value的值还是为0的话(言外之意就是没有被其他线程修改过),则将修改后的副本值刷回主内存更新value的值。所以上面的例子运行结果应该是true和1:
将上面的例子修改为:
publicclassAtomticIntegerTest{ publicstaticvoidmain(String[]args){ AtomicIntegeratomicInteger=newAtomicInteger(0);booleanfirstResult=atomicInteger.compareAndSet(0,1);booleansecondResult=atomicInteger.compareAndSet(0,1);System.out.println(firstResult);System.out.println(secondResult);System.out.println(atomicInteger.get());}}上面例子中,main线程第二次调用compareAndSet方法的时候,value的值已经被修改为1了,不符合其expect的值,所以修改将失败。上面例子输出如下:
CAS底层原理查看compareAndSet方法源码:
/***Atomicallysetsthevalueto{ @codenewValue}*ifthecurrentvalue{ @code==expectedValue},*withmemoryeffectsasspecifiedby{ @linkVarHandle#compareAndSet}.**@paramexpectedValuetheexpectedvalue*@paramnewValuethenewvalue*@return{ @codetrue}ifsuccessful.Falsereturnindicatesthat*theactualvaluewasnotequaltotheexpectedvalue.*/publicfinalbooleancompareAndSet(intexpectedValue,intnewValue){ returnU.compareAndSetInt(this,VALUE,expectedValue,newValue);}该方法通过调用unsafe类的compareAndSwapInt方法实现相关功能。compareAndSwapInt方法包含四个参数:
this,当前对象;
valueOffset,value成员变量的内存偏移量(也就是内存地址):
privatestaticfinallongvalueOffset;static{ try{ valueOffset=unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));}catch(Exceptionex){ thrownewError(ex);}}expect,期待值;
update,更新值。
所以这个方法的含义为:获取当前对象value成员变量在主内存中的值,和传入的期待值相比,如果相等则说明这个值没有被别的线程修改过,然后将其修改为更新值。
那么unsafe又是什么?它的compareAndSwapInt方法是原子性的么?查看该方法的源码:
/***AtomicallyupdatesJavavariableto{ @codex}ifitiscurrently*holding{ @codeexpected}.**<p>Thisoperationhasmemorysemanticsofa{ @codevolatile}read*andwrite.CorrespondstoCatomic_compare_exchange_strong.**@return{ @codetrue}ifsuccessful*/@HotSpotIntrinsicCandidatepublicfinalnativebooleancompareAndSetInt(Objecto,longoffset,intexpected,intx);该方法并没有具体Java代码实现,方法通过native关键字修饰。由于Java方法无法直接访问底层系统,Unsafe类相当于一个后门,可以通过该类的方法直接操作特定内存的数据。Unsafe类存在于sun.msic包中,JVM会帮我们实现出相应的汇编指令。Unsafe类中的CAS方法是一条CPU并发原语,由若干条指令组成,用于完成某个功能的一个过程。原语的执行必须是连续的,在执行过程中不允许被中断,不会存在数据不一致的问题。
getAndIncrement方法剖析了解了CAS原理后,我们回头看下AtomicInteger的getAndIncrement方法源码:
/***Atomicallyincrementsthecurrentvalue,*withmemoryeffectsasspecifiedby{ @linkVarHandle#getAndAdd}.**<p>Equivalentto{ @codegetAndAdd(1)}.**@returnthepreviousvalue*/publicfinalintgetAndIncrement(){ returnU.getAndAddInt(this,VALUE,1);}该方法通过调用unsafe类的getAndAddInt方法实现相关功能。继续查看getAndAddInt方法的源码:
/***Atomicallyaddsthegivenvaluetothecurrentvalueofafield*orarrayelementwithinthegivenobject{ @codeo}*atthegiven{ @codeoffset}.**@paramoobject/arraytoupdatethefield/elementin*@paramoffsetfield/elementoffset*@paramdeltathevaluetoadd*@returnthepreviousvalue*@since1.8*/@HotSpotIntrinsicCandidatepublicfinalintgetAndAddInt(Objecto,longoffset,intdelta){ intv;do{ v=getIntVolatile(o,offset);}while(!weakCompareAndSetInt(o,offset,v,v+delta));returnv;}结合源码,我们便可以很直观地看出为什么AtomicInteger的getAndIncrement方法是线程安全的了:
o是AtomicInteger对象本身;offset是AtomicInteger对象的成员变量value的内存地址;delta是需要变更的数量;v是通过unsafe的getIntVolatile方法获得AtomicInteger对象的成员变量value在主内存中的值。dowhile循环中的逻辑为:用当前对象的值和var5比较,如果相同,说明该值没有被别的线程修改过,更新为v+delta,并返回true(CAS);否则继续获取值并比较,直到更新完成。
CAS的缺点CAS并不是完美的,其存在以下这些缺点:
如果刚好while里的CAS操作一直不成功,那么对CPU的开销大;
只能确保一个共享变量的原子操作;
存在ABA问题。
CAS实现的一个重要前提是需要取出某一时刻的数据并在当下时刻比较交换,这之间的时间差会导致数据的变化。比如:thread1线程从主内存中取出了变量a的值为A,thread2页从主内存中取出了变量a的值为A。由于线程调度的不确定性,这时候thread1可能被短暂挂起了,thread2进行了一些操作将值修改为了B,然后又进行了一些操作将值修改回了A,这时候当thread1重新获取CPU时间片重新执行CAS操作时,会发现变量a在主内存中的值仍然是A,所以CAS操作成功。
解决ABA问题那么如何解决CAS的ABA问题呢?由上面的阐述课件,光通过判断值是否相等并不能确保在一定时间差内值没有变更过,所以我们需要一个额外的指标来辅助判断,类似于时间戳,版本号等。
JUC为我们提供了一个AtomicStampedReference类,通过查看它的构造方法就可以看出,除了指定初始值外,还需指定一个版本号(戳):
/***Createsanew{ @codeAtomicStampedReference}withthegiven*initialvalues.**@paraminitialReftheinitialreference*@paraminitialStamptheinitialstamp*/publicAtomicStampedReference(VinitialRef,intinitialStamp){ pair=Pair.of(initialRef,initialStamp);}我们就用这个类来解决ABA问题,首先模拟一个ABA问题场景:
publicclassAtomticIntegerTest{ publicstaticvoidmain(String[]args){ AtomicReference<String>atomicReference=newAtomicReference<>("A");newThread(()->{ //模拟一次ABA操作atomicReference.compareAndSet("A","B");atomicReference.compareAndSet("B","A");System.out.println(Thread.currentThread().getName()+"线程完成了一次ABA操作");},"thread1").start();newThread(()->{ //让thread2先睡眠2秒钟,确保thread1的ABA操作完成try{ TimeUnit.SECONDS.sleep(2);}catch(InterruptedExceptione){ e.printStackTrace();}booleanresult=atomicReference.compareAndSet("A","B");if(result){ System.out.println(Thread.currentThread().getName()+"线程修改值成功,当前值为:"+atomicReference.get());}},"thread2").start();}}运行程序,输出如下:
使用AtomicStampedReference解决ABA问题:
publicclassAtomicIntegerextendsNumberimplementsjava.io.Serializable{ ......privatevolatileintvalue;/***CreatesanewAtomicIntegerwiththegiveninitialvalue.**@paraminitialValuetheinitialvalue*/publicAtomicInteger(intinitialValue){ value=initialValue;}......}0程序输出如下:
Linux基础组件之无锁消息队列ypipe/yqueue详解
CAS定义
比较并交换(compare and swap, CAS),在多线程编程中用于实现不被打断的数据交换,避免数据不一致问题。该操作通过比较内存值与指定数据,当数值相同则替换内存数据。
为什么需要无锁队列
锁引起的问题:cache损坏/失效、同步机制上的争抢、动态内存分配。
有锁导致线程切换引发cache损坏
大量线程切换导致cache数据失效,处理器与主存之间数据传输效率下降,影响性能。
在同步机制上的争抢队列
阻塞队列导致任务暂停或睡眠,大量时间浪费在获取互斥锁,而非处理数据,引发严重争用。
动态内存分配
多线程中动态分配内存导致互斥,线程频繁分配内存影响应用性能。
无锁队列的实现
无锁队列由ypipe_t和yqueue_t类构成,适用于一读一写场景。通过chunk模式批量分配结点,减少动态内存分配的互斥问题。批量分配大小根据业务场景调整,通常设置较大较为安全。利用spare_chunk存储未释放的chunk,降低频繁分配释放。预写机制减少CAS调用。巧妙的唤醒机制,读端等待无数据时进入等待状态,写端根据返回值判断队列是否为空以唤醒读端。
无锁队列使用
yqueue.write(count,false)用于写入元素并标记完成状态,yqueue.flush()使读端可见更新后数据。yqueue.read(&value)读取元素,返回true表示读到元素,返回false表示队列为空。
ypipe_t使用
write(val, false)更新写入位置,flush()刷新数据到管道,read()读取数据并更新可读位置。
yqueue_t构造函数
初始化队列,end_chunk总是指向最后分配的chunk,back_chunk仅在有元素插入时指向对应的chunk。
front()和back()函数
返回队列头和尾的可读写元素位置。
push()和pop()函数
push()更新写入位置,pop()更新读取位置并检测释放chunk,保持数据流。
源码分析
yqueue_t内部使用chunk批量分配,减少内存操作,spare_chunk存储释放的chunk以供再次使用。ypipe_t构建单写单读无锁队列,通过CAS操作控制读写位置,实现高效数据交换。
ypipe_t / yqueue_t无锁队列利用chunk机制避免频繁内存动态分配,提升性能。通过局部性原理复用回收的chunk,减少资源消耗。flush()检测队列状态通知唤醒,优化数据交换过程。