分布式调度之lock_free_qunne.c介绍
这一片主要是介绍lock_free_quene.c中的代码知识。
lock_free是一种线程中的无锁安全算法。无锁编程是指在不使用锁的情况下,在多线程环境下实现多变量的同步。即在没有线程阻塞的情况下实现同步。这样可以避免竞态、死锁等问题。
Lock-free 算法的基础是 CAS (Compareand-Swap) 原子操作。当某个地址的原始值等于某个比较值时,把值改成新值,无论有否修改,返回这个地址的原始值。目前的cpu 支持最多64位的CAS。并且指针 p 必须对齐。
注:原子操作指一个cpu时钟周期内就可以完成的操作,不会被其他线程干扰。
一般的 CAS 使用方式是:
- 假设有指针 p, 它指向一个 32 位或者64位数,
- 复制p 的内容(*p)到比较量 cmp (原子操作)
- 基于这个比较量计算一个新值 xchg (非原子操作)
- 调用 CAS 比较当前 *p 和 cmp, 如果相等把 *p 替换成 xchg (原子操作)
- 如果成功退出,否则回到第一步重新进行
第3步的 CAS 操作保证了写入的同时 p 没有被其他线程更改。如果*p已经被其他线程更改,那么第2步计算新值所使用的值(cmp)已经过期了,因此这个整个过程失败,重新来过。多线程环境下,由于 3 是一个原子操作,那么起码有一个线程(最快执行到3)的CAS 操作可以成功,这样整体上看,就保证了所有的线程上在“前进”,而不需要使用效率低下的锁来协调线程,更不会导致死锁之类的麻烦。
代码解释
这里是最基础的创建功能,函数根据参数创建一个队列。
参数解释:
- size:参数确定一个队列的大小
- count:要申请队列的个数
函数中功能很明确,就是创建队列,这里使用到一个total变量,主要是用来计算总的size,这里计算方式sizecount+1,这里申请到空间后把该结构体的read和write置为0,itemsize表示的是单个队列的尺寸大小,这里的totalsize表示的是一共申请了count个队列后总的size大小。
这里我也不太明白为啥计算时候为啥要加一,个人理解就是sizecount。。。。。。
//lock-free队列的创建
LockFreeQueue *LFQUE_Create(int size, int count)
{
if (size <= 0 || count <= 0) {
return NULL;
}
//判断参数是否合法
int total = size * count + 1;
//total计算为队列个数乘以队列size,然后+1,这里不太清楚加一的意义
if (total <= 0) {
return NULL;
}
//
register LockFreeQueue *queue = (LockFreeQueue *)SAMGR_Malloc(sizeof(LockFreeQueue) + total);
if (queue == NULL) {
return NULL;
}
queue->write = 0;
queue->read = 0;
queue->itemSize = size;
queue->totalSize = total;
return queue;
}
LFQUE_IsEmpty函数
这个函数主要用来判空lock_free的队列。这是一个bool函数,能够根据传入的队列来判断队列中是否为空。
判空原理就是队列中写的可写元素和可读元素是否相同,因为队列中write位置和read位置之后最开始是相等的,也就是没有元素空状态的时候,一旦开始读写这两个变量不会有
BOOL LFQUE_IsEmpty(LockFreeQueue *queue)
{
return (queue->write == queue->read);
}
LFQUE_IsFull
队列的判满函数,来判断队列中是否元素饱和,达到满状态。这里的totalsize比实际size大1,可能就是为了这里的判满,指针后移一位如果刚好size等于totalsize,说明队列元素已经满了。
BOOL LFQUE_IsFull(LockFreeQueue *queue)
{
uint32 nextWrite = queue->write + 1;
//这里相当于一个指针
if (nextWrite >= queue->totalSize) {
nextWrite = 0;
}
return (nextWrite == queue->read);
}
出队和入队函数push&pop
1.push函数
函数就是将一个指定元素压入队列。
参数:
- quene:要压入元素的队列
- element:要压队的元素
- pri:这个变量在函数中基本没有调用,这里我也不太清楚目的
这里就是队队列剩余空间进行计算,看看能不能入队,如果空间满了就说明线程忙,无法入队。如果剩余空间足够一个itemsize,就将元素在一个itemsize中全部写入。如果不足一个itemsize就将剩余空间写入
int LFQUE_Push(LockFreeQueue *queue, const void *element, uint8 pri)
{
(void)pri;
if (queue == NULL || element == NULL) {
return EC_INVALID;
}
//先检查参数
if (LFQUE_IsFull(queue)) {
return EC_BUSBUSY;
}
//对队列进行判满,如果队列元素满了就无法入队了
uint32 copyLen = (queue->totalSize - queue->write < queue->itemSize) ?
(queue->totalSize - queue->write) : queue->itemSize;
//每次写入判断剩余空间是否可用
if (memcpy_s(&queue->buffer[queue->write], copyLen, element, copyLen) != EOK) {
return EC_INVALID;
}
//对队列缓冲区剩余位置进行初始化,内容为传入的element
//等效于入队,相当于把元素element压入队
element += copyLen;
copyLen = queue->itemSize - copyLen;
//对队列剩余可用空间进行统计
if (copyLen > 0) {
if (memcpy_s(queue->buffer, copyLen, element, copyLen) != EOK) {
return EC_INVALID;
}
}
//对所有写入元素进行统计
uint32 write = queue->write + queue->itemSize;
if (write >= queue->totalSize) {
//如果write超过总地size大小,就做一次差,相当于取模
write = write - queue->totalSize;
}
//
queue->write = write;
//队列write指针改为开始位置
return EC_SUCCESS;
}
压队时候这里是有一定原子性,就是itemsize的空间是一个整体,如果空间足够就选择一整个item,如果不够就选择剩余空间。这里的write指针也如果超出总size就会向totalsize取模一次,保证步长为itemsize。
pop函数
出队的机制基本和入队类似,这里也是一个itemsize全部出队,将队列首部元素写进element,如果空间大于itemsize,则读出一整个itemsize,如果不足一个itemsize,就全部拿出。体现了一个原子性。个人理解!
int LFQUE_Pop(LockFreeQueue *queue, void *element, uint8 *pri)
{
(void)pri;
if (queue == NULL || element == NULL) {
return EC_INVALID;
}
if (LFQUE_IsEmpty(queue)) {
return EC_FAILURE;
}
uint32 copyLen = (queue->totalSize - queue->read < queue->itemSize) ?
(queue->totalSize - queue->read) : queue->itemSize;
if (memcpy_s(element, copyLen, &queue->buffer[queue->read], copyLen) != EOK) {
return EC_FAILURE;
}
element += copyLen;
copyLen = queue->itemSize - copyLen;
if (copyLen > 0) {
if (memcpy_s(element, copyLen, queue->buffer, copyLen) != EOK) {
return EC_FAILURE;
}
}
uint32 read = queue->read + queue->itemSize;
if (read >= queue->totalSize) {
read = read - queue->totalSize;
}
queue->read = read;
return EC_SUCCESS;
}
lock_free_quene队列是一个FIFO机制,是指无锁的队列。并发事务的处理有一定特殊性,没有锁的限制,全靠队列的机制实现。
无锁队列基本原理
感谢阅读!