这一篇分析出队和批量出队操作. 上一篇讲解的很粗糙,但是把主要逻辑讲出来了,大家知道他做了什么就行了.
最后写一个总结,分析一下这个并发队列高效的地方到底在哪.但是并不代表完结,因为这个开源项目还提供了显式生产者.但是这个需要过一段时间才能分享出来了.
测试代码
int main()
{
ConcurrentQueue<int> conqueue;
std::thread t1([&]{
vector<int> vec{1, 2, 3, 4, 5};
conqueue.EnqueueBulk(vec.begin(), vec.size());
});
std::thread t2([&]{
vector<int> results;
int tmp = 0;
while (conqueue.TryDequeue(tmp))
results.push_back(tmp);
});
t1.detach();
t2.detach();
while (true);
return 0;
}
上面代码定义了两个线程,用于模仿真实生产者-消费者模型.生产者和消费者不是同一个线程.来看它是怎么处理的.
对于出队的函数,都是TryXXX这样的函数.返回true就代表元素.
咱们从ConcurrentQueue
类里面TryDequeue
函数是怎么写的
ConcurrentQueue的TryDequeue源码剖析
上面代码,从producer_list_tail_
遍历之前创建的生产者.还记得第一篇中入队操作都需要创建生产者吗?
下面给出一个调用链
回到TryDequeue
函数里,一共遍历3次,找出元素个数最多的那个生产者.大家不要看到就糊涂了,是这一次出队我们选择元素最多的那个生产者. 我们通过while
循环会再次调用这个函数的.
然后调用生产者的Dequeue
函数,获取元素出队.
如果获取元素失败的话,那么找一个生产者尝试出队.
Ok,这个函数到这里结束,让我们把视角又回到生产者的Dequeue
函数
隐式生产者的Dequeue出队
在分析这个函数之前,先介绍两个成员变量.这俩我觉得挺有意思的,学到了
在隐式生产者的基类ProducerBase
定义了两个成员变量,分别是乐观出队计数器和过度提交计数器
作者在这里引入这俩主要就是优化多线程环境下的出队操作,减少锁竞争带来的开销.
大家以后也可以尝试在自己的代码加入这种机制.
首先根据乐观计数器-过度提交计数器是否大于tail_index_
.如果大于那就说明真没数据了,直接返回false.
如果小于的情况,将乐观计数器加1,再次获取tail_index
,这都是没有用锁的.
再次判断是否小于tail_index
,用了两次验证提高真的有数据的准确性.然后将head_index_
加1,指向下一个要读取的内容下标. 获取对应的块索引条目项,从块中获取之前入队的数据. 调用移动构造将数据移动到参数中. 期间还会针对移动构造出现异常的情况. 最后设置块的空块数量,如果这个块已经没有任何数据存储了,那么直接加入空闲链表中(调用ConncurrentQueue
类的函数).
在这里,我觉得没有验证获取出来得数据正确性.上面两次乐观计数器判断成功了,但是实际上真的没有数据可读了,那么获取出来的就是一个假的,上面代码没有验证最后向用户返回true,但其实是一个假的数据,这肯定会造成业务逻辑上的bug
就在这里没有验证数据是真的还是假的.
隐式生产者的DequeueBulk批量出队
未完待续...
等待更新,先处理最近手头事情