简单无锁队列的实现和使用

无锁队列越来越流行,在特定的场合使用不同的无锁队列,可以起到节省锁开销,提高程序效率。

Linux内核中有无锁队列的实现,可谓简洁而不简单。核心判断部分利用了整数溢出机制,这个有很多文章专门介绍,我们就不详细讲了。

里面注释很详细,直接来kfifo的源码,大家看源码注释应该就可以理解了。源代码是linux上实现的,为了跨平台,增加了其他平台下的实现。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define __u32 unsigned long
#define __u64 unsigned long long

#if defined(__GNUC__)
#define min(x,y) ({ 		\
        typeof(x) _x = (x); \
        typeof(y) _y = (y);	\
        (void) (&_x == &_y);\
        _x < _y ? _x : _y; })

#define max(x,y) ({ 		\
        typeof(x) _x = (x);	\
        typeof(y) _y = (y);	\
        (void) (&_x == &_y);\
        _x > _y ? _x : _y; })

#else
#define max(a,b)            (((a) > (b)) ? (a) : (b))
#define min(a,b)            (((a) < (b)) ? (a) : (b))
#endif

#define MAX_KFIFO_SIZE 0x1000

struct kfifo { 
    unsigned char *buffer; /* the buffer holding the data */ 
    unsigned int size; /* the size of the allocated buffer */ 
    unsigned int in; /* data is added at offset (in % size) */ 
    unsigned int out; /* data is extracted from off. (out % size) */ 
};

/**
 * fls - find last bit set
 * @x: the word to search
 *
 * This is defined the same way as ffs:
 * - return 32..1 to indicate bit 31..0 most significant bit set
 * - return 0 to indicate no bits set
 */
#if defined(__GNUC__)
static inline int fls(int x)
{
    int r;

    __asm__("bsrl %1,%0\n\t"
            "jnz 1f\n\t"
            "movl $-1,%0\n"
            "1:" : "=r" (r) : "rm" (x));
    return r+1;
}
#else
static inline int fls(int x)
{
	int position;
	int i;
	if(0 != x)
	{
		for (i = (x >> 1), position = 0; i != 0; ++position)
			i >>= 1;
	}
	else
	{
		position = -1;
	}		
	return position+1;
}
#endif
/**
 * fls64 - find last bit set in a 64-bit value
 * @n: the value to search
 *
 * This is defined the same way as ffs:
 * - return 64..1 to indicate bit 63..0 most significant bit set
 * - return 0 to indicate no bits set
 */
static inline int fls64(__u64 x)
{
    __u32 h = x >> 32;
    if (h)
        return fls(h) + 32;
    return fls(x);
}

static inline unsigned fls_long(unsigned long l)
{
    if (sizeof(l) == 4)
        return fls(l);
    return fls64(l);
}

static inline unsigned long roundup_pow_of_two(unsigned long x)
{
    return 1UL << fls_long(x - 1);
}

/**
 * * kfifo_alloc - allocates a new FIFO and its internal buffer
 * * @size: the size of the internal buffer to be allocated.
 * * @gfp_mask: get_free_pages mask, passed to kmalloc()
 * * @lock: the lock to be used to protect the fifo buffer
 * *
 * * The size will be rounded-up to a power of 2.
 * */
struct kfifo *kfifo_alloc(unsigned int size)   
{   
    unsigned char *buffer;   
    struct kfifo *fifo;   
  
    /*  
     *       * round up to the next power of 2, since our 'let the indices  
     *            * wrap' tachnique works only in this case.  
     *                 */   
    if (size & (size - 1)) {   
            if(size > 0x80000000);
				return NULL;
            size = roundup_pow_of_two(size);            
        }
  
    buffer = (unsigned char *)malloc(size);   
    if (!buffer)   
        return NULL;   
  
    fifo = (struct kfifo*)malloc(sizeof(struct kfifo));   
  
    if (!fifo)   
    {
         free(buffer);
         return NULL;
    }

    fifo->buffer = buffer;
    fifo->size = size;
    fifo->in = fifo->out = 0;
  
    return fifo;   
} 

/**
 * * kfifo_free - frees the FIFO
 * * @fifo: the fifo to be freed.
 * */
void kfifo_free(struct kfifo *fifo)
{
    free(fifo->buffer);
    free(fifo);
}

/**
* __kfifo_put - puts some data into the FIFO, no locking version
* @fifo: the fifo to be used.
* @buffer: the data to be added.
* @len: the length of the data to be added.
*
* This function copies at most @len bytes from the @buffer into
* the FIFO depending on the free space, and returns the number of
* bytes copied.
*
* Note that with only one concurrent reader and one concurrent
* writer, you don't need extra locking to use these functions.
*/
unsigned int __kfifo_put(struct kfifo *fifo,
                        const unsigned char *buffer, unsigned int len)
{
        unsigned int l;

        len = min(len, fifo->size - fifo->in + fifo->out);

        /* first put the data starting from fifo->in to buffer end */
        l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
        memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);

        /* then put the rest (if any) at the beginning of the buffer */
        memcpy(fifo->buffer, buffer + l, len - l);

        fifo->in += len;

        return len;
}

/**
* __kfifo_get - gets some data from the FIFO, no locking version
* @fifo: the fifo to be used.
* @buffer: where the data must be copied.
* @len: the size of the destination buffer.
*
* This function copies at most @len bytes from the FIFO into the
* @buffer and returns the number of copied bytes.
*
* Note that with only one concurrent reader and one concurrent
* writer, you don't need extra locking to use these functions.
*/
unsigned int __kfifo_get(struct kfifo *fifo,
                         unsigned char *buffer, unsigned int len)
{
        unsigned int l;

        len = min(len, fifo->in - fifo->out);

        /* first get the data from fifo->out until the end of the buffer */
        l = min(len, fifo->size - (fifo->out & (fifo->size - 1)));
        memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), l);

        /* then get the rest (if any) from the beginning of the buffer */
        memcpy(buffer + l, fifo->buffer, len - l);

        fifo->out += len;

        return len;
}

/**
* __kfifo_reset - removes the entire FIFO contents, no locking version
* @fifo: the fifo to be emptied.
*/
static inline void __kfifo_reset(struct kfifo *fifo)
{
        fifo->in = fifo->out = 0;
}

/**
* __kfifo_len - returns the number of bytes available in the FIFO, no locking version
* @fifo: the fifo to be used.
*/
static inline unsigned int __kfifo_len(struct kfifo *fifo)
{
        return fifo->in - fifo->out;
}
使用的部分写了一个类,采用了模板封装,提供了模板类型存取的使用方法。

template <typename T>
class zFifo
{
    private:
        kfifo* _kfifo;
    public:
        zFifo()
        {
            _kfifo = kfifo_alloc(MAX_KFIFO_SIZE);
        }
        ~zFifo()
        {
            if(NULL != _kfifo)
              kfifo_free(_kfifo);
        }
        bool push(T data);
        T get();
};

template <typename T>
bool zFifo<T>::push(T data)
{
    int len = 0;
    len = __kfifo_put(_kfifo, (const unsigned char *)&data, sizeof(T));
    if(len > 0)
        return true;
    else
        return false;
}

template <typename T>
T zFifo<T>::get()
{
    T data;
    int len = __kfifo_get(_kfifo, (unsigned char *)&data, sizeof(T));
    if(len > 0)
        return data;
    else
        return NULL;
}

这种库一般都用在需要高效处理的地方,为了减少内存拷贝,一般都使用指针的形式操作。一个简单的使用例子:

int main()
{
    zFifo<int*> zf;
    int a = 1;
    zf.push(&a);
    printf("a=%d\n", &a);
    int* b = NULL;
    b = zf.get();
    printf("b=%d\n", b);

    return 0;
}
需要注意的地方:

1.只有一个线程负责读,另一个线程负责写的时候,数据是线程安全的。上面的实现是基于这个原理实现的,当有多个线程读或者多个线程写的时候,不保证数据的正确性。
所以使用的时候,一个线程写,一个线程读。网络应用中比较常用,就是开一个线程接口数据,然后把数据写入队列。然后开一个调度线程读取网络数据,然后分发到处理线程。

2.数据长度默认宏定义了一个长度,超过这个长度的时候,后续的数据会写入失败。

<think>嗯,用户让我介绍一下如何使用STL实现无锁队列。首先,我需要明确几个关键点。无锁队列是一种并发数据结构,它允许多线程同时访问而无需使用互斥锁。STL是C++的标准模板库,但标准库中的容器通常不是线程安全的,更不用说无锁了。所以用户的问题可能有些矛盾,因为直接使用STL容器实现无锁队列可能不可行,但也许需要结合STL的一些组件原子操作来实现。 首先,我得回忆一下无锁编程的基本概念。无锁结构通常依赖于原子操作内存顺序来确保线程安全。STL中提供了<atomic>头文件,里面有std::atomic模板,可以用来实现原子变量。但STL的容器如queue或list本身不是设计为无锁的,所以可能需要自己用原子操作节点结构来构建。 用户可能想了解如何利用STL提供的原子操作一些数据结构,比如智能指针或者节点结构,来手动实现一个无锁队列。比如,可以使用std::atomic<Node*>来表示头尾指针,然后通过CAS(比较并交换)操作来更新指针,保证线程安全。 接下来,我需要考虑无锁队列的常见实现方式,比如Michael-Scott算法,这是一种经典的基于链表的无锁队列。算法中使用两个指针,headtail,入队出队操作通过原子操作来更新这些指针。这时候,可能需要定义一个节点结构体,包含数据下一个节点的原子指针。 然后,STL中的std::atomic是否足够支持这些操作?比如,对于指针的原子操作,std::atomic<Node*>确实支持load、store、compare_exchange_weak等操作,这些是实现无锁队列所必需的。 另外,内存管理的问题也需要考虑。当节点被弹出队列后,可能有其他线程还在访问它,因此不能立即释放内存。这时候可能需要使用风险指针(hazard pointers)或者引用计数等技术来安全地回收内存。STL中的智能指针如std::shared_ptr可能在这里派上用场,但需要检查其原子操作的支持情况。不过,std::shared_ptr的原子操作可能会有性能问题,或者需要特定的内存顺序。 还需要考虑ABA问题,即在CAS操作中,指针的值可能被其他线程释放并重新分配,导致虽然地址相同但内容不同。这可以通过使用带标记的指针或者使用双重宽度的CAS指令来解决,但C++的std::atomic是否支持这些需要确认。例如,在某些平台上,可以使用std::atomic<T*>结合位操作来处理标记,但可能不够便携。 然后,可能需要给出一个简单的示例代码,展示如何用std::atomic节点结构实现无锁队列的基本结构。但需要注意,完整的无锁队列实现非常复杂,涉及到正确的内存顺序ABA问题的处理,用户可能需要了解这些限制。 总结一下,步骤可能包括: 1. 定义节点结构,包含数据原子指针。 2. 使用std::atomic<Node*>表示头尾指针。 3. 实现入队出队操作,使用CAS操作更新指针。 4. 处理内存回收问题,可能需要结合STL的智能指针或其他机制。 5. 处理ABA问题内存顺序,确保线程安全。 用户可能对无锁编程不太熟悉,因此需要解释这些概念,并指出直接使用STL容器无法实现无锁队列,必须自己构建结构,结合原子操作。此外,需要提醒用户无锁编程的复杂性潜在陷阱,比如正确性性能问题。</think>以下是使用C++ STL结合原子操作实现无锁队列的简要指南: ```cpp #include <atomic> #include <memory> template<typename T> class LockFreeQueue { private: struct Node { std::shared_ptr<T> data; // 使用智能指针管理数据 std::atomic<Node*> next; Node(T value) : data(std::make_shared<T>(std::move(value))), next(nullptr) {} }; std::atomic<Node*> head; std::atomic<Node*> tail; public: LockFreeQueue() { Node* dummy = new Node(T{}); // 哨兵节点 head.store(dummy); tail.store(dummy); } void enqueue(T value) { Node* newNode = new Node(std::move(value)); while(true) { Node* oldTail = tail.load(); Node* next = oldTail->next.load(); // 确保tail仍指向原节点 if(oldTail == tail.load()) { if(next == nullptr) { // 尝试原子性更新next指针 if(oldTail->next.compare_exchange_weak(next, newNode)) { // 推进tail指针 tail.compare_exchange_weak(oldTail, newNode); return; } } else { // 帮助其他线程完成尾指针推进 tail.compare_exchange_weak(oldTail, next); } } } } bool dequeue(T& result) { while(true) { Node* oldHead = head.load(); Node* oldTail = tail.load(); Node* next = oldHead->next.load(); if(oldHead == head.load()) { if(oldHead == oldTail) { if(next == nullptr) return false; // 队列为空 // 推进tail指针 tail.compare_exchange_weak(oldTail, next); } else { // 尝试取出数据 result = *next->data; // 推进head指针 if(head.compare_exchange_weak(oldHead, next)) { delete oldHead; // 内存回收存在风险 return true; } } } } } }; ``` 关键实现要点: 1. 基于链表的Michael-Scott算法 - 使用哨兵节点简化边界条件处理 - 包含headtail两个原子指针 - 入队/出队操作采用CAS原子指令 2. 内存管理问题 - 使用std::shared_ptr自动管理数据内存 - 节点删除存在ABA问题(示例代码未完全解决) - 生产环境建议使用内存回收方案(Hazard Pointer/EBR) 3. 原子操作选择 - compare_exchange_weak实现无锁保证 - memory_order_relaxed可优化性能(需谨慎) - 保证可见性的内存屏障设置 实际注意事项: 1. ABA问题解决方案: - 使用带标签的指针(指针地址+版本号) - 依赖平台特定的双字CAS指令(如CMPXCHG16B) 2. 完整实现应包含: - 迭代器支持 - 容量限制 - 异常安全保证 - 动态内存回收 3. 性能优化方向: - 批量操作支持 - 缓存行对齐(alignas(64)) - 避免false sharing 替代方案参考: 1. 使用boost::lockfree::queue 2. 基于环形缓冲区的实现 3. 结合TSX事务内存(Intel® TSX) 建议优先使用成熟的无锁库(如Folly的AtomicHashMap),自行实现需进行严格的多线程压力测试。无锁数据结构虽然避免了锁竞争,但在高并发场景下可能产生更复杂的缓存一致性开销。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值