有锁队列、无锁队列

一、lock-free(无锁)

  • 系统作为一个整体无论如何都向前移动(至少有一个线程向前移动),不能保证每个线程的前进进度(可能出现线程饿死)。
    • 使用互斥锁不能保证系统作为一个整体无论如何都向前移动:如果当前操作临界资源的线程出现异常了,没有释放锁,就会导致其他线程无法向前移动。如果队列为空,所有的消费者线程都会处于阻塞状态。
  • 通常使用 compare_exchange 原语实现。
  • 可以有循环,但类似 compare_exchange 实现的自旋锁不行。

二、wait-free(无等待)

  • 在其他线程争夺锁、有的线程发生阻塞等情况下,每个线程都向前移动,每个操作在有限步骤中执行。
  • 通常使用 exchangefetch_add 等原语实现,并且不包含可能被其他线程影响的循环

三、blocking(有锁)

  • 整个系统可能不会取得任何进展,阻塞、中断或终止的线程可能无限地阻止系统范围内的向前。

四、无锁队列

  • 使用原子变量、原子操作,但是不像互斥锁、自旋锁那样:需要对一个变量做标记、以及有一个循环不断地去检测这个标记,设置标记。
  • 无锁队列是一种特殊类型的队列,它在多线程环境下允许数据的并发访问和修改,而无需使用传统的锁机制来实现线程同步。
  • 什么时候使用无锁队列 ?
    • 先用有锁队列进行实现,然后分析是否需要保证系统向前移动,需要的话用无锁队列进行优化
    • 队列中的任务执行时间较短,可以用无锁队列;队列中的任务执行时间较长,还是使用有锁队列
    • 只能使用无锁队列的情况:
      • 信号处理程序:
        • 信号处理程序可以在程序执行的任何时刻被调用,包括在其他信号处理程序或在持有锁的代码执行期间。
        • 如果信号处理程序尝试获取一个已经被持有的锁,它将无法获取锁,导致程序挂起或死锁,因为持有锁的线程可能正在等待信号处理程序完成。
      • 实时系统:执行时间有严格的上限。

五、生产者消费者队列

  • 生产者 — 消费者队列是并发系统中最基本的组件之一。
  • 根据允许的生产者和消费者线程的数量,可以划分为:
    • MPMC:多生产者 / 多消费者队列
    • SPMC:单生产者 / 多消费者队列。
    • MPSC:多生产者 / 单消费者队列
    • SPSC:单生产者 / 单消费者队列。
  • 根据底层数据结构,可以划分为:
    • Array-based:基于数组。
      • 基于数组的队列通常更快,但是它们通常不是严格无锁的。
      • 缺点是它们需要为最坏的情况预先分配内存。
    • Linked-list-based:基于链表。
      • 链表队列是动态增长的,因此不需要预先分配任何内存。
    • Hybrid:混合队列。
      • 结合数组和队列的优点。
  • 根据链表队列是否是侵入式的,可以划分为:
    • Intrusive:侵入式:插入的任务作为节点内存的一部分。
    • Non-intrusive:非侵入式:插入的任务作为链表中的一个节点。
    • 如果需要转换已经动态分配的数据,则侵入式队列通常具有更好的性能,因为不需要管理额外的节点内存。
      • 在准备 Node 的时候,会跟 cache line 进行匹配,那么这些 Node 的访问性能会更高。
  • 根据链表队列长度的最大大小,可以划分为:
    • Bounded:有界。
    • Unbounded:无界。
    • 无界队列很危险,通常需要一个有界队列,因为它将强制执行你认为应该发生的事情,如有界队列中溢出部分如何处理(丢弃或者覆盖最老的数据),从而避免无法控制的事情。

六、有锁队列实现

  • 最基本的有锁队列,采用互斥锁,各种情况都可以使用,主要用于任务执行时间长、有耗时运算的情况。消费者线程不会被阻塞
#ifndef MARK_LOCKEDQUEUE_H
#define MARK_LOCKEDQUEUE_H

#include <deque>
#include <mutex>

template <class T, typename StorageType = std::deque<T> >
class LockedQueue
{
    //! Lock access to the queue.
    std::mutex _lock;

    //! Storage backing the queue.
    StorageType _queue;

    //! Cancellation flag.
    volatile bool _canceled;

public:

    //! Create a LockedQueue.
    LockedQueue()
        : _canceled(false)
    {
    }

    //! Destroy a LockedQueue.
    virtual ~LockedQueue()
    {
    }

    //! Adds an item to the queue.
    void add(const T& item)
    {
        lock();

        _queue.push_back(item);

        unlock();
    }

    //! Adds items back to front of the queue
    template<class Iterator>
    void readd(Iterator begin, Iterator end)
    {
        std::lock_guard<std::mutex> lock(_lock);
        _queue.insert(_queue.begin(), begin, end);
    }

    //! Gets the next result in the queue, if any.
    bool next(T& result)
    {
        std::lock_guard<std::mutex> lock(_lock);

        if (_queue.empty())
            return false;

        result = _queue.front();
        _queue.pop_front();

        return true;
    }

    template<class Checker>
    bool next(T& result, Checker& check)
    {
        std::lock_guard<std::mutex> lock(_lock);

        if (_queue.empty())
            return false;

        result = _queue.front();
        if (!check.Process(result))
            return false;

        _queue.pop_front();
        return true;
    }

    //! Peeks at the top of the queue. Check if the queue is empty before calling! Remember to unlock after use if autoUnlock == false.
    T& peek(bool autoUnlock = false)
    {
        lock();

        T& result = _queue.front();

        if (autoUnlock)
            unlock();

        return result;
    }

    //! Cancels the queue.
    void cancel()
    {
        std::lock_guard<std::mutex> lock(_lock);

        _canceled = true;
    }

    //! Checks if the queue is cancelled.
    bool cancelled()
    {
        std::lock_guard<std::mutex> lock(_lock);
        return _canceled;
    }

    //! Locks the queue for access.
    void lock()
    {
        this->_lock.lock();
    }

    //! Unlocks the queue.
    void unlock()
    {
        this->_lock.unlock();
    }

    ///! Calls pop_front of the queue
    void pop_front()
    {
        std::lock_guard<std::mutex> lock(_lock);
        _queue.pop_front();
    }

    ///! Checks if we're empty or not with locks held
    bool empty()
    {
        std::lock_guard<std::mutex> lock(_lock);
        return _queue.empty();
    }
    int size() {
        std::lock_guard<std::mutex> lock(_lock);
        return _queue.size();
    }
};
#endif
#include "LockedQueue.h"

#include <thread>
#include <iostream>

// g++ main_lockedqueue.cpp -o locked_queue -lpthread -std=c++11

class Check1 {
public:
    bool Process(int val) {
        return val % 2 == 0;
    }
};

class Check2 {
public:
    bool Process(int val) {
        return val % 2 != 0;
    }
};

int main() {

    LockedQueue<int> queue;

    std::thread pd1([&]() {
        queue.add(1);
        queue.add(2);
        queue.add(3);
        queue.add(4);
    });

    std::thread pd2([&]() {
        queue.add(5);
        queue.add(6);
        queue.add(7);
        queue.add(8);
    });

    std::thread cs1([&]() {
        std::this_thread::sleep_for(std::chrono::microseconds(1000));
        int ele;
        Check1 check;
        while(!queue.empty()) {
            if (queue.next(ele, check)) {
                std::cout << "cs1: " << std::this_thread::get_id() << " : pop " << ele << std::endl;
            }
        }
    });
    std::thread cs2([&]() {
        std::this_thread::sleep_for(std::chrono::microseconds(10));

        int ele;
        Check2 check;
        while(!queue.empty()) {
            if (queue.next(ele, check)) {
                std::cout << "cs2: " << std::this_thread::get_id() << " : pop " << ele << std::endl;
            }
        }
    });

    pd1.join();
    pd2.join();
    cs1.join();
    cs2.join();
    
    return 0;
}
  • 适用于各种情况。消费者线程会被阻塞
    • 多生产者多消费者的情况下,锁的碰撞比较大:生产者和生产者时刻发生碰撞;生产者和消费者发生碰撞;消费者和消费者发生碰撞。

#ifndef _MARK_PC_QUEUE_H
#define _MARK_PC_QUEUE_H

#include <condition_variable>
#include <mutex>
#include <queue>
#include <atomic>
#include <type_traits>

template <typename T>
class ProducerConsumerQueue
{
private:
    std::mutex _queueLock;
    std::queue<T> _queue;
    std::condition_variable _condition;
    std::atomic<bool> _shutdown;

public:

    ProducerConsumerQueue<T>() : _shutdown(false) { }

    void Push(const T& value)
    {
        std::lock_guard<std::mutex> lock(_queueLock);
        _queue.push(std::move(value));

        _condition.notify_one();
    }

    bool Empty()
    {
        std::lock_guard<std::mutex> lock(_queueLock);

        return _queue.empty();
    }

    size_t Size() const
    {
        return _queue.size();
    }

    bool Pop(T& value)
    {
        std::lock_guard<std::mutex> lock(_queueLock);

        if (_queue.empty() || _shutdown)
            return false;

        value = _queue.front();

        _queue.pop();

        return true;
    }

    void WaitAndPop(T& value)
    {
        std::unique_lock<std::mutex> lock(_queueLock);

        while (_queue.empty() && !_shutdown)
            _condition.wait(lock);

        if (_queue.empty() || _shutdown)
            return;

        value = _queue.front();

        _queue.pop();
    }

    void Cancel()
    {
        std::unique_lock<std::mutex> lock(_queueLock);

        while (!_queue.empty())
        {
            T& value = _queue.front();

            DeleteQueuedObject(value);

            _queue.pop();
        }

        _shutdown = true;

        _condition.notify_all();
    }

private:
    template<typename E = T>
    typename std::enable_if<std::is_pointer<E>::value>::type DeleteQueuedObject(E& obj) { delete obj; }

    template<typename E = T>
    typename std::enable_if<!std::is_pointer<E>::value>::type DeleteQueuedObject(E const& /*packet*/) { }
};

#endif

七、无锁队列实现

  • 仅适用于多生产者单消费者。
#ifndef _MARK_MPSC_QUEUE_H
#define _MARK_MPSC_QUEUE_H

#include <atomic>
#include <utility>

template<typename T>
class MPSCQueueNonIntrusive
{
public:
    MPSCQueueNonIntrusive() : _head(new Node()), _tail(_head.load(std::memory_order_relaxed))
    {
        Node* front = _head.load(std::memory_order_relaxed);
        front->Next.store(nullptr, std::memory_order_relaxed);
    }

    ~MPSCQueueNonIntrusive()
    {
        T* output;
        while (Dequeue(output))
            delete output;

        Node* front = _head.load(std::memory_order_relaxed);
        delete front;
    }

// wait-free
    void Enqueue(T* input)
    {
        Node* node = new Node(input);
        Node* prevHead = _head.exchange(node, std::memory_order_acq_rel);
        prevHead->Next.store(node, std::memory_order_release);
    }

    bool Dequeue(T*& result)
    {
        Node* tail = _tail.load(std::memory_order_relaxed);
        Node* next = tail->Next.load(std::memory_order_acquire);
        if (!next)
            return false;

        result = next->Data;
        _tail.store(next, std::memory_order_release);
        delete tail;
        return true;
    }

private:
    struct Node
    {
        Node() = default;
        explicit Node(T* data) : Data(data)
        {
            Next.store(nullptr, std::memory_order_relaxed);
        }

        T* Data;
        std::atomic<Node*> Next;
    };

    std::atomic<Node*> _head;
    std::atomic<Node*> _tail;

    MPSCQueueNonIntrusive(MPSCQueueNonIntrusive const&) = delete;
    MPSCQueueNonIntrusive& operator=(MPSCQueueNonIntrusive const&) = delete;
};

template<typename T, std::atomic<T*> T::* IntrusiveLink>
class MPSCQueueIntrusive
{
public:
    MPSCQueueIntrusive() : _dummyPtr(reinterpret_cast<T*>(std::addressof(_dummy))), _head(_dummyPtr), _tail(_dummyPtr)
    {
        // _dummy is constructed from aligned_storage and is intentionally left uninitialized (it might not be default constructible)
        // so we init only its IntrusiveLink here
        std::atomic<T*>* dummyNext = new (&(_dummyPtr->*IntrusiveLink)) std::atomic<T*>();
        dummyNext->store(nullptr, std::memory_order_relaxed);
    }

    ~MPSCQueueIntrusive()
    {
        T* output;
        while (Dequeue(output))
            delete output;
    }

    void Enqueue(T* input)
    {
        (input->*IntrusiveLink).store(nullptr, std::memory_order_release);
        T* prevHead = _head.exchange(input, std::memory_order_acq_rel);
        (prevHead->*IntrusiveLink).store(input, std::memory_order_release);
    }

    bool Dequeue(T*& result)
    {
        T* tail = _tail.load(std::memory_order_relaxed);
        T* next = (tail->*IntrusiveLink).load(std::memory_order_acquire);
        if (tail == _dummyPtr)
        {
            if (!next)
                return false;

            _tail.store(next, std::memory_order_release);
            tail = next;
            next = (next->*IntrusiveLink).load(std::memory_order_acquire);
        }

        if (next)
        {
            _tail.store(next, std::memory_order_release);
            result = tail;
            return true;
        }

        T* head = _head.load(std::memory_order_acquire);
        if (tail != head)
            return false;

        Enqueue(_dummyPtr);
        next = (tail->*IntrusiveLink).load(std::memory_order_acquire);
        if (next)
        {
            _tail.store(next, std::memory_order_release);
            result = tail;
            return true;
        }
        return false;
    }

private:
    std::aligned_storage_t<sizeof(T), alignof(T)> _dummy;
    T* _dummyPtr;
    std::atomic<T*> _head;
    std::atomic<T*> _tail;

    MPSCQueueIntrusive(MPSCQueueIntrusive const&) = delete;
    MPSCQueueIntrusive& operator=(MPSCQueueIntrusive const&) = delete;
};

template<typename T, std::atomic<T*> T::* IntrusiveLink = nullptr>
using MPSCQueue = std::conditional_t<IntrusiveLink != nullptr, MPSCQueueIntrusive<T, IntrusiveLink>, MPSCQueueNonIntrusive<T>>;

#endif // MPSCQueue_h__

  • 25
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值