KFIFO入门笔记
一、 KFIFO的逻辑流程
- 申请一块固定空间,作为循环队列。
- 入队操作优先copy数据,然后下标in加一。
- 出队操作优先取出数据,然后下标out加一。
- 为保证内存一致性顺序,使用内存屏障保证下标加一操作在内存copy之后执行。
- 下标加一是否要加锁或使用CAS操作,根据具体情况而定,linux下测试uint32_t不用加锁,windows要加锁或使用CAS。
- 注意以上操作仅适用于单生产者单消费者场景,多生产者,多消费者场景可以分别在两端加锁,使其模拟单生产者单消费者场景。
- 保证无锁也能保证单消费者线程和单生产者线程操作数据正确的原理是:
- 首先保证下标in和out的操作的原子性(使用CAS函数等方式)
- 其次是最重要的,保证下标加一操作在内存操作之后执行,这个不仅仅是将内存操作代码写在下标移动代码之前就能实现的,因为程序在执行时,基于操作系统对于性能优化的考虑,会调整命令执行顺序,导致内存操作这种耗时大的命令,即使代码在下标移动命令之前,也会可能在下标移动命令之后执行,导致消费者线程读取下标后,但对应的内存还未拷贝完毕,从而导致数据操作错误,而内存屏障可以解决这个问题。详细的原理介绍,可以搜索
缓存一致性
。
二、无锁队列的几种实现方式
- RingBuffer
- KFifo
- SPSC
- Disruptor
三、涉及的技术
- CAS指令
- 内存屏障
四、需要了解的知识
- 缓存一致性顺序
五、KFIFO实现代码
环境:linux , C++11
my_kifo.h
//
// Created by hapYP on 8/26/22.
// Implement a non-lock queue.
//
#ifndef MY_KFIFO_MY_KFIFO_H
#define MY_KFIFO_MY_KFIFO_H
#if 0
#include <stdint.h>
#include <pthread.h>
#define min(x, y) ((x) < (y) ? (x) : (y))
#define smp_wmb() __asm__ __volatile__("": : :"memory")
//typedef void* FifoElemType;
struct KFIFO {
uint32_t in;
uint32_t out;
uint32_t mask; // size - 1;
uint32_t eSize; // element size
void *data;
};
/**
* @brief: Initialize a struct KFIFO.
* @param:
* @retType:
* @retVal:
* @bug:
**/
extern int KFifoInit(struct KFIFO *_kfifo, uint32_t _size, uint32_t _eSize);
/**
* @brief:
* @param:
* @retType:
* @retVal:
* @bug:
**/
extern uint32_t KFifoInLock(struct KFIFO *_kFifo, const void *_buf, uint32_t _len, pthread_spinlock_t *_lock);
/**
* @brief: Copy the data started at _buf and length _len to _kFifo;
* @param:
* @retType: uint32_t
* @retVal: The length copy into kFifo successfully. less than or equal _len;
* @bug:
**/
extern uint32_t KFifoIn(struct KFIFO *_kFifo, const void *_buf, uint32_t _len);
/**
* @brief:
* @param:
* @retType:
* @retVal:
* @bug:
**/
static void KFifoCopyIn(struct KFIFO *_kFifo, const void *src, uint32_t _len, uint32_t _off);
/**
* @brief:
* @param:
* @retType:
* @retVal:
* @bug:
**/
extern uint32_t KFifoOut(struct KFIFO *_kFifo, void *_buf, uint32_t _wantCopyOutLen);
/**
* @brief:
* @param:
* @retType:
* @retVal:
* @bug:
**/
static void KFifoCopyOut(struct KFIFO *_kFifo, void *_buf, uint32_t _copyOutLen, uint32_t _off);
/**
* @brief:
* @param:
* @retType:
* @retVal:
* @bug:
**/
extern uint32_t KFifoUnused(struct KFIFO *_kFifo);
/**
* @brief:
* @param:
* @retType:
* @retVal:
* @bug:
**/
extern int IsFifoEmpty();
/**
* @brief:
* @param:
* @retType:
* @retVal:
* @bug:
**/
extern int IsFifoFull();
/**
* @brief:
* @param:
* @retType:
* @retVal:
* @bug:
**/
extern int KFifoDestroy();
/**
* @brief: Get the minimum of power 2 and greater than _value
* @param:
* @retType:
* @retVal:
* @bug:
**/
extern uint64_t MinUpPowerTwo(uint32_t _value);
#endif
#include <stdint.h>
#define Min(x, y) ((x) > (y) ? (y) : (x))
#define smp_wmb() __asm__ __volatile__("": : :"memory")
/*
* Define the struct describes the KFifo.
*/
struct KFIFO{
uint32_t in; // push in
uint32_t out; // pop out
uint32_t mask; // total size - 1
uint32_t elemSize; // the size of element type
void *pData; // the first address of memory for saving the data.
};
/**
* @brief: Initialize struct KFIFO and alloc memory for KFIFO.pData.
* @param: _pKfifo: The start address of struct KFIFO.
* @param: _size: The total bytes of the KFIFO.pData wanting to alloc.
* @param: _elemSize: The bytes of element type.
* @ret: 0: success
* @ret: -1: fail
* @bug: None
**/
int KFifoInit(struct KFIFO *_pKFifo, uint32_t _size, uint32_t _elemSize);
/**
* @brief: Calculate the minimum which is greater than _num and is the power of two.
* @param: _num: number be calculated.
* @ret: Calculate result.
* @bug:
**/
uint64_t RoundupPowOfTwo(uint32_t _num);
/**
* @brief: Return the count of unused block.
* @param: _pKFifo:
* @ret:
*