c++实现LinkBlockedQueue的问题
c++链表实现的阻塞队列

最近从java源码里发现了阻塞队列的实现,觉得非常有趣。
首先,介绍下什么是阻塞队列。阻塞队列代表着一个队列可以线程安全的往该队列中写数据和从该队列中读数据。也就是说,我们可以在多个线程之间并发的进行写数据和读数据,而不会引发任何并发问题。
下面我们就说说如何实现一个阻塞队列。
而实现一个阻塞队列的前提:
- 需要能够使用链表实现一个队列
- 能够使用c++的锁机制,去给队列的写和读操作加锁。
为了性能,这里的读和写的锁不能是同一把锁。而对于一个链表队列来说,读取操作肯定需要涉及头指针,写操作肯定涉及尾指针。既然要实现读操作一把锁和写操作一把锁。那么就要求读操作只能更改头指针而不能更改尾指针,写操作只能更改尾指针而不能更改头指针。不满足这个要求,那么读写操作就不可能实现用两把锁分别对读写进行加锁。
基本队列的实现
现在我们先说说如何实现这个队列。
要求:入队操作(enqueue)只能操作尾指针(last), 出队操作(dequeue)只能操作头指针(head)。
对于队列的初始化,这里不能单纯的设置为空指针,需要将头尾指针同一节点。
下面我们来看入队操作如何实现
从这个入队操作来看,该操作只更改了尾指针last, 而没有更改头指针head。
其代码实现为:
void enqueue(int item) {
Node *node = new Node(item);
last = last->next = node;
}
接下来我们来看出队操作如何实现
从出队操作来看就有趣的多,它抛弃了head所指向的节点,而这个节点有可能是那些节点呢?
- 初始化时所赋值的那个节点
- 出队后的节点
也就是说,head所指向的节点中的val值没有任何实际含义。当需要出队时,出队head指向的下一个节点first中val的值,然后抛弃head本身指向的值,让head指向head的下一个节点first,此时head原来所指向的节点将被删除。现在我们可以看出出队操作也只改变了头指针head的值。
其代码实现为:
int dequeue() {
Node *h = head;
Node *first = head->next;
delete h;
head = first;
int x = first->item;
return x;
}
现在队列已经实现,下面就看看阻塞队列如何实现。
阻塞队列的实现
既然是阻塞队列,那就意味这加锁和等待。那就需要对c++的一些锁知识和条件变量有了解。
先来看看我们需要实现阻塞队列的那些方法:
| Special Value | Blocks | Times out | |
|---|---|---|---|
| Insert | offer(o) | put(o) | offer(o,timeout) |
| Remove | poll() | take() | poll(timeout) |
入队
让我们先来实现put这个方法。
先看其实现流程图:
由于该队列实现有最大值限制,故在插入数据之前需要先判断该队列是否已满,已满则需等待该队列有可用空间。在该线程入队操作完成后,可能有别的线程也在等待入队,需要唤醒其他写数据的线程,使其继续执行后续操作。如果入队前队列为空,可能有出队操作正在阻塞等待读数,也需要去唤醒读数据的线程。
在看代码实现之前,我们需要定义一些变量用于代码实现环节:
/* The capacity bound*/ int64_t capacity; /*Current number of elements */ std::atomic<int64_t> count; /** Lock held by take, pool, etc */ std::mutex takeLock; /** Wait queue for waiting takes */ std::condition_variable notEmpty; /** Lock held by put, offer, etc */ std::mutex putLock; /** Wait queue for waiting puts */ std::condition_variable notFull;
put函数的代码实现:
void LinkedBlockingQueue::put(const int item){
int c;
{
std::unique_lock<std::mutex> lck{putLock};
if( count == capacity) {
notFull.wait(lck, [this](){return count < capacity;}); //(1)
}
enqueue(item); //不应该把申请空间放在锁里面,耗时有点大
c = count.fetch_add(1);
}
if(c + 1 < capacity) {
notFull.notify_one();
}
if(0 == c) {
notEmpty.notify_one();
}
}
对于offer(o)的实现,主要更改是对上述代码(1)中的等待改为直接返回false, 表示,当前没有可用空间插入数据。正常插入就返回true.
对于offer(o,timeout)的实现,就需要在上述代码(1)中的wait函数添加上时间参数,使其可以在timeout时间内返回,如果是正常唤醒,正常入队,则返回ture,否则返回false.
该更改为:
if(!notFull.wait_for(lck, rel_time, [this](){return count < capacity;})){
return false;
}
出队
对于出队,其实现和入队基本相同,基本上只需要更改其中的关键判断和通知。
take函数的代码实现为:
void LinkedBlockingQueue::take(int & returnVal) {
int c;
{
std::unique_lock<std::mutex> lck{takeLock};
if( 0 == count) {
notEmpty.wait(lck, [this](){return count > 0 ;});
}
returnVal = dequeue();
c = count.fetch_sub(1);
}
if( c > 1 ) {
notEmpty.notify_one();
}
if(c == capacity) {
notFull.notify_one();
}
}
剩下的实现细节可以参考我的实现
代码知识SEO上一篇 : Vue检测屏幕变化来改变不同的charts样式实例
下一篇 : Spring Boot 使用 Swagger 构建 RestAPI 接口文档
-
SEO外包最佳选择国内专业的白帽SEO机构,熟知搜索算法,各行业企业站优化策略!
SEO公司
-
可定制SEO优化套餐基于整站优化与品牌搜索展现,定制个性化营销推广方案!
SEO套餐
-
SEO入门教程多年积累SEO实战案例,从新手到专家,从入门到精通,海量的SEO学习资料!
SEO教程
-
SEO项目资源高质量SEO项目资源,稀缺性外链,优质文案代写,老域名提权,云主机相关配置折扣!
SEO资源
-
SEO快速建站快速搭建符合搜索引擎友好的企业网站,协助备案,域名选择,服务器配置等相关服务!
SEO建站
-
快速搜索引擎优化建议没有任何SEO机构,可以承诺搜索引擎排名的具体位置,如果有,那么请您多注意!专业的SEO机构,一般情况下只能确保目标关键词进入到首页或者前几页,如果您有相关问题,欢迎咨询!