无锁队列一(简单实现)

 

没多少行代码,先给出最终实现:

template<typename T>                                                            
class CAS {                                                                     
private:                                                                        
    T *pt_;                                                                     
    T old_val_;                                                                 
    T new_val_;                                                                 
public:                                                                         
    CAS(T *pt, T old_val, T new_val) {                                          
        pt_ = pt;                                                               
        old_val_ = old_val;                                                     
        new_val_ = new_val;                                                     
        while (!__sync_bool_compare_and_swap(pt, old_val, new_val));            
    }                                                                           
    ~CAS() {                                                                    
        if (new_val_ != __sync_val_compare_and_swap(pt_, new_val_, old_val_)) { 
            fprintf(stderr, "~CAS Error!\n");                                   
        }                                                                       
    }                                                                           
};

template<typename T>                                                            
class LockFreeQueue {                                                           
private:                                                                        
    T *queue_ptr_;                                                              
    int64_t mask_;                                                              
    int64_t __attribute((aligned (0x40))) head_;                                
    int64_t head_count_;                                                        
    int64_t head_pos_;                                                          
    int64_t __attribute((aligned (0x40))) tail_;                                
    int64_t tail_count_;                                                        
    int64_t tail_pos_;                                                          
                                                                                
    // head_count_ = |[head_pos_, tail_pos_)|                                   
    // tail_count_ = |(tail_pos_, head_pos_)|                                   
                                                                                
public:                                                                         
    LockFreeQueue(int64_t size) {                                               
        if (size <= 1 || size & (size - 1) != 0) {                              
            fprintf(stderr, "LockFreeQueue Error!");                            
            exit(1);                                                            
        }                                                                       
        mask_ = size - 1;                                                       
        queue_ptr_ = new T[size];                                               
                                                                                
        head_ = 0;                                                              
        tail_ = 0;                                                              
                                                                                
        head_pos_ = 0;                                                          
        tail_pos_ = size - 1;                                                   
                                                                                
        head_count_ = (tail_pos_ - head_pos_) & mask_;
        tail_count_ = (head_pos_ - tail_pos_ - 1) & mask_;                      
    }                                                                           
                                                                                
    // A full memory barrier around CAS should be guaranteed!                   
                                                                                
    // If queue is full return false,                                           
    // otherwise return true.                                                   
                                                                                
    //ease::Mutex en, de;                                                       
                                                                                
    bool Enqueue(T el) {                                                        
        //ease::MutexLock lock(&en);                                            
        CAS<int64_t> cas(&head_, 0, 1);                                         
                                                                                
        if (head_count_ == 0) {                                                 
            head_count_ = (tail_pos_ - head_pos_) & mask_;                      
            if (head_count_ == 0) return false;                                 
        }                                                                       
        head_count_--;                                                          
                                                                                
        queue_ptr_[head_pos_] = el;                                             
        __sync_synchronize();                                                   
        head_pos_ = (head_pos_ + 1) & mask_;                                    
                                                                                
        return true;                                                            
    }

    // If queue is emtpy return false,                                          
    // otherwise return true.                                                   
    bool Dequeue(T &el) {                                                       
        //ease::MutexLock lock(&de);                                            
        CAS<int64_t> cas(&tail_, 0x00, 0x01);                                   
                                                                                
        if (tail_count_ == 0) {                                                 
            tail_count_ = (head_pos_ - tail_pos_ - 1) & mask_;                  
            if (tail_count_ == 0) return false;                                 
        }                                                                       
        tail_count_--;                                                          
                                                                                
        el = queue_ptr_[(tail_pos_ + 1) & mask_];                               
        __sync_synchronize();                                                   
        tail_pos_ = (tail_pos_ + 1) & mask_;                                    
                                                                                
        return true;                                                            
    }                                                                           
};

这个实现的特点是两头分别是串行控制,但可以同步进行。

另外加入head_count_,tail_count_优化,可以减少两头信息同步带来的开销。

 

实现中几个注意的点:

1、

不大相关的变量组放在不同的cache line中,

这样可以避免它们所在的cache line作无谓的同步。

例如head_,tail_。

2、

一次调用中相关的,甚至是相互依赖的变量可以放在一个cache line中,

把数据集中起来更方便cpu处理,例如head_,head_pos_,head_count_。

3、

把常量性质的变量跟经常需要线程间同步的变量,放在不同的cache line中。

例如queue_ptr_, mask_,它们被读的时候,所在的cache line是不需要去同步的。

 

以上3点根据测试知是可以提升性能的。

 

4、

el = queue_ptr_[(tail_pos_ + 1) & mask_];                               
__sync_synchronize();                                                   
tail_pos_ = (tail_pos_ + 1) & mask_;

确保Enqueue线程看到数据出队操作是先于位置标志变化的,

不然标志变化先被Enqueue感知了,就可能导致原先的数据还没出队就被覆盖了。

5、

CAS的系统调用中会有一个full memory产生,这也是正确性的保证。

 

实例化一个无锁队列(LockFreeQueue<uint64_t> lfq(1 << 10);)

测试1000W个数据进出队列的所用的时间:

1 en-threads, 1 de-threads:
[test0] time consume: 1393ms
[test1] time consume: 1270ms
[test2] time consume: 1374ms
2 en-threads, 2 de-threads:
[test0] time consume: 1366ms
[test1] time consume: 1557ms
[test2] time consume: 1353ms
3 en-threads, 3 de-threads:
[test0] time consume: 3899ms
[test1] time consume: 4409ms
[test2] time consume: 4614ms
4 en-threads, 4 de-threads:
[test0] time consume: 8159ms
[test1] time consume: 8664ms
[test2] time consume: 11048ms
5 en-threads, 5 de-threads:
[test0] time consume: 11903ms
[test1] time consume: 11153ms
[test2] time consume: 12082ms

不难看出随着线程数的增加耗时也大概成线性增加。

另外把CAS换成MutexLock,测试结果是:

1 en-threads, 1 de-threads:                                                     
[test0] time consume: 2218ms                                                    
[test1] time consume: 2100ms                                                    
[test2] time consume: 2159ms                                                    
2 en-threads, 2 de-threads:                                                     
[test0] time consume: 2305ms                                                    
[test1] time consume: 2301ms                                                    
[test2] time consume: 2338ms                                                    
3 en-threads, 3 de-threads:                                                     
[test0] time consume: 2449ms                                                    
[test1] time consume: 2631ms                                                    
[test2] time consume: 2550ms                                                    
4 en-threads, 4 de-threads:                                                     
[test0] time consume: 2526ms                                                    
[test1] time consume: 2527ms                                                    
[test2] time consume: 2524ms                                                    
5 en-threads, 5 de-threads:                                                     
[test0] time consume: 2523ms                                                    
[test1] time consume: 2550ms                                                    
[test2] time consume: 2528ms

MutexLock随着线程数打增加,耗时增加没有CAS那么陡,相对比较平缓,

所以MutexLock开了很多线程仍然保持3000ms左右,当然开多了,耗时会越来越大。

 

可以得出的结论是当线程总数不大于cpu总核数的时候,无锁有一定的优势,

但是线程开多了,线程之间的cpu竞争将带来越来越多的开销。

 

这个实现比较简单,下来想一种两头都可以并行的做法。

<think>嗯,用户让我介绍下如何使用STL实现无锁队列。首先,我需要明确几个关键点。无锁队列种并发数据结构,它允许多线程同时访问而无需使用互斥锁。STL是C++的标准模板库,但标准库中的容器通常不是线程安全的,更不用说无锁了。所以用户的问题可能有些矛盾,因为直接使用STL容器实现无锁队列可能不可行,但也许需要结合STL的些组件和原子操作来实现。 首先,我得回忆下无锁编程的基本概念。无锁结构通常依赖于原子操作和内存顺序来确保线程安全。STL中提供了<atomic>头文件,里面有std::atomic模板,可以用来实现原子变量。但STL的容器如queue或list本身不是设计为无锁的,所以可能需要自己用原子操作和节点结构来构建。 用户可能想了解如何利用STL提供的原子操作和些数据结构,比如智能指针或者节点结构,来手动实现无锁队列。比如,可以使用std::atomic<Node*>来表示头尾指针,然后通过CAS(比较并交换)操作来更新指针,保证线程安全。 接下来,我需要考虑无锁队列的常见实现方式,比如Michael-Scott算法,这是种经典的基于链表的无锁队列。算法中使用两个指针,head和tail,入队和出队操作通过原子操作来更新这些指针。这时候,可能需要定义个节点结构体,包含数据和下个节点的原子指针。 然后,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算法 - 使用哨兵节点简化边界条件处理 - 包含head和tail两个原子指针 - 入队/出队操作采用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),自行实现需进行严格的多线程压力测试。无锁数据结构虽然避免了锁竞争,但在高并发场景下可能产生更复杂的缓存致性开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值