为什么需要无锁队列呢?(分模块讲解)

一:加锁和无锁的区别?

在说锁之前,咱们先讲一下关于锁的几种状态。

1、blocking(锁)

较弱保证系统向前移动,例如自旋锁,互斥锁等。为什么是较弱保证系统向前移动呢?因为这个加锁队列无论是什么操作,基本上都有锁来进行控制,如果当加上锁之后,但是有不明的信号将这个加锁操作中断了,那么这个锁就不会被(释放)解锁,那么这个队列就死掉了,而消费者线程就饿死了。

2、lock_free(无锁)

较强保证系统向前移动,例如:CAS原子操作,他们允许有限的循环。如果要实现无锁队列,只能基于原子操作,内存屏障等实现。

3、wait_free(无等待)

强保证系统向前移动,例如:exchange,fetch_add。这个无等待是对于消费者线程来说的,因为前面的blocking队列会阻塞消费者线程,怎么阻塞的呢?是通过互斥锁和条件变量这两个一起实现的,当队列为空的时候,条件变量会阻塞在消费者线程。但是这个wait_free是不阻塞的,因此没有条件变量。

讲完前面三种类型的队列之后,还要讲一下生产消费者队列。生产消费者队列是并发系统中最基本的组件之一,但是没有一个标准。但是根据生产消费者的数量可以划分为:

MPMC:多生产者/多消费者。

SPMC:单生产者/多消费者。

MPSC:多生产者/单消费者。

SPSC:单生产者/单消费者。

虽然MPMC什么样都可以用,但是当一生产者一消费者时,SPSC会更快于MPMC,所以对于选择什么样的队列,还要是看实际应用。

二:为什么要使用无锁队列?

大家都知道加锁肯定是需要时间的,而对于多线程环境下锁的开销会更大:线程切换、上下文切换、cache损坏。所以有时候时间会浪费在保护队列的争夺上面,而并非执行任务。当然上面也讲到了blocking队列被打断的情况(信号处理程序),因此对于这种信号处理程序以及硬实时系统(执行时间有限)的情况来说,无锁队列会更加实用。

三:无锁队列是不是比有锁队列性能高?

回答这个问题之前,我们要知道怎么去衡量性能的高低。是单位时间内插入元素的个数以及单位时间取出元素的个数吗?并不是这样的。

举个夸张的栗子:有锁的时间:2秒,无锁的时间:1秒。而处理程序的时间都一样:1秒。这样看好像无锁的效率更高,但是情况肯定是多样的,当处理任务的时间:10秒。这个时候对于加锁和无锁差的那1秒好像就不那么重要了,所以有锁队列更适合耗时任务。所以看性能的高低是通过看:单位时间内处理任务的个数来决定的。

四:看几个新队列的模块

1:msg_queue(blocking)适合多生产者多消费者场景。

        (1):结构体

        下面的结构体好多都有两份,为什么呢?是因为这个模型使用了两个队列,一个供生产者使用,一个供消费者使用,每个队列都要配一套。那有两个队列的话,消费者怎么得到数据呢?其实是生产者收数据,然后当消费者队列为空的时候,消费者队列尝试与生产者队列进行交换,如果此时的生产者队列也为空的话,那消费者就进行阻塞等待。只有当消费者队列为空,才会与生产者队列发生碰撞,其他情况生产者只碰撞生产者,消费者只碰撞消费者。https://xxetb.xetslk.com/s/2D96kH

struct __msgqueue
{
	size_t msg_max;		//队列的最大数量
	size_t msg_cnt;		//当前的数量
	int linkoff;        // linkoff  Count 偏移多少个字节就是用于链接下一个节点的next指针
	int nonblock;		//是否阻塞
	void *head1;		//指向生产者
	void *head2;		//指向消费者
	void **get_head;	//拿到消费者的头部
	void **put_head;	//拿到生产者的头部
	void **put_tail;	//生产者的尾指针
	pthread_mutex_t get_mutex;	//消费者的锁
	pthread_mutex_t put_mutex;	//生产者的锁
	pthread_cond_t get_cond;	//消费者的条件变量
	pthread_cond_t put_cond;	//生产者的条件变量
};

        (2):生产者

void msgqueue_put(void *msg, msgqueue_t *queue)
{
	void **link = (void **)((char *)msg + queue->linkoff);	//利用二级指针,我的线程池有讲解

	*link = NULL;
	pthread_mutex_lock(&queue->put_mutex);
	while (queue->msg_cnt > queue->msg_max - 1 && !queue->nonblock)
		pthread_cond_wait(&queue->put_cond, &queue->put_mutex);//比如我要100个,但是现在有101个,那么就发生阻塞

	*queue->put_tail = link;
	queue->put_tail = link;
	queue->msg_cnt++;
	pthread_mutex_unlock(&queue->put_mutex);
	pthread_cond_signal(&queue->get_cond);	//唤醒线程
}

        (3):消费者

void *msg;
	//当消费者要拿数据,将两个队列进行,交换。
	pthread_mutex_lock(&queue->get_mutex);
	if (*queue->get_head || __msgqueue_swap(queue) > 0)	
	{
		msg = (char *)*queue->get_head - queue->linkoff;
		*queue->get_head = *(void **)*queue->get_head;
	}
	else
		msg = NULL;

	pthread_mutex_unlock(&queue->get_mutex);
	return msg;

        (4):交换操作

static size_t __msgqueue_swap(msgqueue_t *queue)
{//将两个队列交换
	void **get_head = queue->get_head;
	size_t cnt;

	queue->get_head = queue->put_head;
	pthread_mutex_lock(&queue->put_mutex);
	while (queue->msg_cnt == 0 && !queue->nonblock)
		pthread_cond_wait(&queue->get_cond, &queue->put_mutex);

	cnt = queue->msg_cnt;
	if (cnt > queue->msg_max - 1)
		pthread_cond_broadcast(&queue->put_cond);

	queue->put_head = get_head;
	queue->put_tail = get_head;
	queue->msg_cnt = 0;
	pthread_mutex_unlock(&queue->put_mutex);
	return cnt;
}

2:locked_queue(wait_free)队列为空不会阻塞消费者线程,适合耗时任务的情况

        (1):所含参数

//为什么说这个队列,当队列为空的时候,是不会阻塞消费者线程呢?,因为他这个没有条件变量,如果有条件变量,会让所有线程阻塞等待信号,
//但是这个没有条件变量,不需要等待,而是通过一个while循环,来进行判断,每次调用他的,在队列中有互斥锁,只有这一把,肯定都要去抢,但是只能一个抢到
//通过模板,这里默认使用队列,此队列
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:

        (2):判断是否为空

///! Checks if we're empty or not with locks held
    bool empty()
    {
        std::lock_guard<std::mutex> lock(_lock);
        return _queue.empty();
    }

        (3):入队操作

//! 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);
    }

        (4):出队操作

T& peek(bool autoUnlock = false)
    {
        lock();

        T& result = _queue.front();

        if (autoUnlock)      //用于检查队列是否为空  
            unlock();

        return result;
    }

3:对于dpdk rte_ring无锁队列,我感觉有点难,讲的不太好,所以大家可以看看别人的讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值