1.手摸手Go 深入理解sync.Cond
2.ReentrantLock源码详细解析
3.EasyLogger源码学习笔记(1)
4.AbstractQueuedSynchronizer详解
手摸手Go 深入理解sync.Cond
sync.Cond 是同步同步一个用于等待一个或一组goroutines满足条件后唤醒的条件变量实现。它关联一个锁,源码通常是代码 *Mutex 或 RWMutex,具体根据需求初始化。同步同步
sync.Cond的源码基本用法以实现FIFO队列为例。我们定义了队列的代码大唐飞鸟娱乐源码Offer和Pop操作。一个goroutine不断向队列投放数据,同步同步同时有两个goroutines不断取拿数据。源码Cond帮助我们管理队列的代码访问。
在使用sync.Mutex时,同步同步需要理解其核心功能。源码具体地,代码Cond.wait()的同步同步使用遵循特定模板,其逻辑将在后续分析中详细说明。源码
sync.Cond的代码数据结构包含三个部分:noCopy、Locker和copyChecker。noCopy确保实例不可复制,Locker用于互斥访问,copyChecker检查Cond实例是否被复制。当实例被复制时,会检查并确保内存地址的正确性。
sync.Cond的notifyList包含三类字段,用于管理等待的goroutines。整体结构简单明了。
sync.Cond的核心操作Wait()分为五个步骤,主要完成两个任务:等待和加入等待链表。Signal操作唤醒一个等待时间最长的goroutine,无需持有锁。洗冤录源码Broadcast()与Signal()区别在于它可以唤醒全部等待的goroutine,并直接更新等待链表。
总结sync.Cond的使用,它提供了一种高效管理goroutines等待和唤醒的机制,简化了多线程编程中的同步问题。通过源码分析,我们深入了解了Cond的内部实现,为实际应用提供了基础。
ReentrantLock源码详细解析
在深入解析ReentrantLock源码之前,我们先了解ReentrantLock与同步机制的关系。ReentrantLock作为Java中引入的并发工具类,由Doug Lea编写,相较于synchronized关键字,它提供了更为灵活的锁管理策略,支持公平与非公平锁两种模式。AQS(AbstractQueuedSynchronizer)作为实现锁和同步器的核心框架,由AQS类的独占线程、同步状态state、FIFO等待队列和UnSafe对象组成。AQS类的内部结构图显示了其组件的构成。在AQS框架下,等待队列采用双向链表实现,头结点存在但无线程,T1和T2节点中的线程可能在自旋获取锁后进入阻塞状态。
Node节点作为等待队列的基本单元,分为共享模式和独占模式,值得关注的爱上头条源码是waitStatus成员变量,它包含五种状态:-3、-2、-1、0、1。本文重点讨论-1、0、1状态,-3状态将不涉及。非公平锁与公平锁的差异在于,非公平锁模式下新线程可直接尝试获取锁,而公平锁模式下新线程需排队等待。
ReentrantLock内部采用非公平同步器作为其同步器实现,构造函数中根据需要选择非公平同步器或公平同步器。ReentrantLock默认采用非公平锁策略。非公平锁与公平锁的区别在于获取锁的顺序,非公平锁允许新线程跳过等待队列,而公平锁严格遵循队列顺序。
在非公平同步器的实例中,我们以T1线程首次获取锁为例。T1成功获取锁后,将exclusiveOwnerThread设置为自身,state设置为1。紧接着,T2线程尝试获取锁,但由于state为1,获取失败。新站网整站源码调用acquire方法尝试获得锁,尝试通过tryAcquire方法实现,非公平同步器的实现调用具体逻辑。
在非公平锁获取逻辑中,通过CAS操作尝试交换状态。交换成功后,设置独占线程。当当前线程为自身时,执行重入操作,叠加state状态。若获取锁失败,则T2和T3线程进入等待队列,调用addWaiter方法。队列初始化通过enq方法实现,enq方法中的循环逻辑确保线程被正确加入队尾。新线程T3调用addWaiter方法入队,队列初始化完成。
在此过程中,T2和T3线程开始自旋尝试获取锁。若失败,则调用parkAndCheckInterrupt()方法进入阻塞状态。在shouldParkAfterFailedAcquire方法中,当前驱节点等待状态为CANCELLED时,方法会找到第一个非取消状态的节点,并断开取消状态的前驱节点与该节点的连接。若T5线程加入等待队列,T3和T4线程因为自旋获取锁失败进入finally块调用取消方法,小马快报源码找到等待状态不为1的节点(即T2),断开连接。
理解了shouldParkAfterFailedAcquire方法后,我们关注acquireQueued方法的实现。该方法确保线程在队列中正确释放,如果队列的节点前驱为head节点,成功获取锁后,调用setHead方法释放线程。setHead方法通过CAS操作更新head节点,释放线程。acquire方法中的阻塞是为防止线程在唤醒后重新尝试获取锁而进行的额外阻断。
锁的释放过程相对简单,将state减至0,将exclusiveOwnerThread设置为null,完成锁的释放。通过上述解析,我们深入理解了ReentrantLock的锁获取、等待、释放等核心机制,为并发编程提供了强大的工具支持。
EasyLogger源码学习笔记(1)
在编程中,预处理器通过宏定义执行特定的逻辑。使用`#ifdef`和`#else`可以实现条件编译。当`#ifdef _XXXX`中的标识符_XXXX被`#define`命令定义时,编译器将执行`#ifdef`后的程序段1,否则执行`#else`后的程序段2。`#ifndef _XXXX`则表示如果标识符未被定义,则执行程序段1,反之执行程序段2。
ANSI C宏提供了多种实用信息,如`__DATE__`返回当前日期,`__TIME__`返回当前时间,`__FILE__`包含当前文件名,`__LINE__`包含当前行号。`__STDC__`常量用于判断程序是否遵循ANSI C标准。`__FUNCTION__`宏在预编译时返回所在函数的名称。
宏参数的处理可以通过`#`将参数变为字符串,使用`##`将两个宏参数连接起来。`__VA_ARGS__`是一个可变参数宏,需配合`define`使用,将宏左侧的`..`内容原样复制到右侧。
`#if defined`和`#if !defined`在功能上相似,都用于判断宏是否定义。`#error`指令在编译时生成错误消息并停止编译,用于警告开发者。
`extern`关键字用于引用其他文件中的函数或全局变量。例如`extern ElogErrCode elog_port_init(void);`声明了一个名为`elog_port_init`的外部函数,调用时需要指明返回值类型和参数。
在多线程编程中,使用`sched_param`结构来管理线程调度参数。`sem_t`表示信号量,用于实现互斥和同步。`pthread_attr_setschedpolicy(&thread_attr, SCHED_RR);`设置进程调度策略为实时轮转调度。
`SCHED_OTHER`默认分时调度策略,`SCHED_FIFO`采用先进先出策略,而`SCHED_RR`是`SCHED_FIFO`的增强版,提供实时轮转功能。使用`sched_get_priority_max(int policy);`和`sched_get_priority_min(int policy);`函数可以获取线程可设置的最高和最低优先级,其中策略参数即上述三种调度策略的宏定义。
`pthread_attr_setschedparam(&thread_attr, &thread_sched_param);`用于设置线程的优先级。通过这些函数,开发者可以精细地控制线程调度,提高程序性能。
AbstractQueuedSynchronizer详解
AbstractQueuedSynchronizer,简称AQS,究竟是什么呢?
它提供了一个框架,用于实现基于先进先出(FIFO)等待队列的阻塞锁及相关同步器(如信号量、事件等)。这个类的设计目的是为了成为那些依赖单一原子整数值来表示状态的同步器的有效基础。
从JDK的注释来看,AQS是一个实现阻塞锁和相关同步器的框架,它基于先进先出的等待队列。这个类适用于大多数使用整数来表示状态的同步器。这里提到的框架实际上是一个类。
那么,在什么情况下我们会使用AQS呢?
在编程中,我们经常遇到并发问题。有了并发,就涉及资源共享;而资源共享又需要处理资源的同步访问。在处理同步时,我们需要解决竞争发生时的等待问题和竞争解除后的唤醒问题。AQS就是这样一个便于实现这种同步机制的框架。我们日常使用的ReentrantLock、ReentrantReadWriteLock以及ArrayBlockingQueue等都是基于AQS实现的。
如果没有AQS,又会如何呢?
在没有AQS的情况下,要实现锁需要怎么做呢?这些问题都是我们在实现锁时需要考虑的。
在AQS的情况下,情况又是怎样的呢?
AQS作为基础类,主要解决了在锁不可用时的等待,以及锁释放后的唤醒。锁状态的设定、如何获取锁以及如何释放锁,都是需要相应的同步机制自己实现的。因此,在使用AQS时,需要实现以下方法:
类似于ReentrantLock,使用state来标识锁的状态,state = 0表示锁未被获取,当state > 0表示锁已被获取。此外,实现了AQS的tryAcquire()和tryRelease()来处理state的状态,以处理锁的状态。这样就可以实现基础的ReentrantLock。因此,在AQS的支持下,实现类似的阻塞锁非常方便。
以上我们介绍了AQS是什么,以及AQS的具体使用场景,下面我们详细介绍AQS的具体实现机制。
我们先简单看一下AQS的核心流程。
接下来,我们将结合ReentrantLock来详细查看基于AQS如何实现排他锁。
首先,我们来看一下ReentrantLock是如何使用int类型的state来表示锁的状态的。state = 0表示锁未被获取。state > 0表示锁已被持有。由于是可重入锁,state表示了这个锁被同一个线程获取了多少次。
上面的代码片段是ReentrantLock的核心代码,为了便于解释,省略了其他代码。首先我们可以看到,在使用AQS时,通常不是直接实现AQS,而是创建一个内部的辅助类。
Subclasses should be defined as non-public internal helper classes that are used to implement the synchronization properties of their enclosing class
然后加锁和释放锁对应的lock和unlock,直接调用辅助类的lock和unlock。
下面我们来看一下Sync类的代码。
上面我们来看一下非公平锁NonfairSync的实现。
下面我们来看一下AQS中的acquire方法。
上面我们看了获取锁的方法acquire,下面看下释放锁的release。
总结一下,AQS通过tryAcquire以及tryRelease两个方法,来进行锁的获取以及释放,两个都是非阻塞的方法。如果获取成功则返回,如果获取失败,则添加队列。在队列中获取锁,获取失败则挂起,等待锁被释放后被重新唤醒。唤醒后还是会去尝试获取锁。释放锁的时候如果检查到已经全部释放,则会唤醒被挂起的线程。这样通过tryAcquire和tryRelease,实现了锁的获取以及等待,以及锁的释放。具体锁状态的控制,子类则是通过tryAcquire和tryRelease进行控制的。
在介绍ReentrantLock时,我们简化了很多代码。了解基本原理后,再去读源码会事半功倍。