入队(EnQueue) 、出队(TryDequeue) 、是否为空(IsEmpty)、获取队列内元素数量(Count)。
一、ConcurrentQueue内部结构:
1.实现原理
众所周知,在普通的非线程安全队列有两种实现方式:
1.使用数组实现的循环队列。
2.使用链表实现的队列。
先看看两种方式的优劣:
.Net Farmework中的普通队列Queue的实现使用了第一种方式,缺点是当队列空间不足会进行扩容,扩容的主要实现是开辟一个原始长度2倍的新数组,然后将原始数组里面的数据复制到新数组中,所以当扩容时就会产生不小的内存开销,在并发的环境中对性能的影响不可小视。当然在调用Queue的构造函数时可以指定默认空间的大小,但是一般情况下数据量是不可预测的,选大了会照成空间浪费,选小了会有复制内存的开销,而且队列扩容以后需要显示调用TrimToSize()方法才能回收掉不使用的内存空间。
第二种链表实现方式虽然消除了空间浪费的问题但是又增加了GC的压力,当入队时会分配一个新节点,出队时要对该节点进行废弃,对于大量的出队入队操作时该实现方式性能不高。
综合以上两种实现方式,在支持多线程并发出队并发入队的情况下,ConcurrentQueue使用了分段存储的概念(如上图所示),ConcurrentQueue分配内存时以段(Segment)为单位,一个段内部含有一个默认长度为32的数组和执行下一个段的指针,有个和Head和Tail指针分别指向了起始段和结束段(这种结构有点像操作系统的段式内存管理和页式内存管理策略)。这种分配内存的实现方式不但减轻的GC的压力而且调用者也不用显示的调用TrimToSize()方法回收内存(在某段内存为空时,会由GC来回收该段内存)。
2.Segment(段)内部结构
其实对于ConcurrentQueue的操作其实就是对Segment(数据段)的操作。
Segment可抽象出如下数据结构:
Segment内部主要方法:
Segment内部和用数组实现的普通队列相当,只不过对于入队和出队操作使用了原子操作来防止多线程竞争问题,使用随机退让等技术保证活锁等问题,实现机制和ConcurrentStack差别不大,跟多TryAppend的实现细节在源码注释中已经阐述的非常清楚这里就再做不过多的解释。
二、入队操作
如上图所示,入队操作是在尾部的段中进行,当数据进入段内失败时会先进行一个回退操作然后再不断尝试直到成功,这里失败的原因(tail.Append(item)返回false)只有一个就是当该段内的空间不够时正在分配新的段,这段时间内会进入该段的元素会失败。
三、出队操作
如上图所示,出队失败时返回false 而不是像入队一样进行回退操作,因为出队失败的原因只有一个就是当队列内所有段的元素为空时,所以出队设计成了返回bool值的函数。
四、判断是否为空(IsEmpty)
整个判断为O(1)的复杂度 主要有三种情况:
1. 头节点(段)不为空返回false
2. 头节点为空而且下一个节点也为空返回true
3. 头节点为空而且下一个节点不为空返回false,这种情况说明队列正在扩容,所以要自选等待扩容完毕时再次进行判断
五、获取队列内元素数量(Count)
找到头节点的low的位置和尾节点的high的位置,由于每个段内记录了当前段在队列中的索引,所以很容易求出整个队列中元素的数量。
跟ConcurrentStack一样 微软官方文档和注释中也说明:判断队列是否为空要使用IsEmpty属性而不是判断Count == 0 原因在于GetHeadTailPositions在大量数据入队和出队的过程中寻找头尾节点的位置是比较耗时的操作,要不断循环确定头尾节点的位置,所以判断队列是否为空还是使用IsEmpty属性。
到此这篇关于c#高效的线程安全队列ConcurrentQueue<T>的实现的文章就介绍到这了,更多相关c# ConcurrentQueue<T>内容请搜索小牛知识库以前的文章或继续浏览下面的相关文章希望大家以后多多支持小牛知识库!
我要创建一个程序,给定N个线程,这些线程可以在队列中插入或删除一个元素,但是线程访问队列是有条件的: null 我用同步块做的,就像这样: run void很简单,它只是在插入或删除元素时永远循环。 我的问题是,在不使用synchronized的情况下,我如何遵循那个条件? 没有同步块,怎么可能保证互斥呢? 编辑:我不能使用类似于同步的东西(就像锁一样)
我必须在一个系统中管理计划的文件复制。文件复制是由用户安排的,我需要限制复制期间使用的系统资源数量。没有定义每次复制可能需要的时间(即,可能计划每15分钟运行一次复制,并且在下一次运行到期时,上一次运行可能仍在运行)。 我有一个调度器,它定期检查到期的文件复制,对于每个文件复制,(1)如果它没有排队也没有运行,就将它添加到阻塞队列中;(2)否则就删除它。 我还有一个线程池,等待直到队列中有复制并执
关于中提到的以下实施: http://doc.akka.io/docs/akka-http/10.0.5/scala/http/client-side/host-level.html 从多个线程提供队列http请求是线程安全的吗?如果不是,那么执行这种要求的最佳方法是什么?也许用一个有奉献精神的演员?
基础的FIFO队列 # queue_fifo.py import queue q = queue.Queue() for i in range(5): q.put(i) while not q.empty(): print(q.get(), end=' ') print() LIFO队列 # queue_lifo.py import queue q = queue.Lif
我编写了一个简单的类,我计划将其扩展为客户端套接字编程应用程序的一部分。类涉及一个BlockingQueue(我从这里复制了代码:相当于Java的BlockingQueue的C++)。当我创建了下面的包装类的一个实例后,我打算让它生成一个单独的线程,该线程只需执行BlockingQueue上阻塞的printer()函数,直到有一个或多个字符串可用,然后它只需将字符串打印到控制台窗口。在我的预期应用
本文向大家介绍详解C++实现线程安全的单例模式,包括了详解C++实现线程安全的单例模式的使用技巧和注意事项,需要的朋友参考一下 在某些应用环境下面,一个类只允许有一个实例,这就是著名的单例模式。单例模式分为懒汉模式,跟饿汉模式两种。 首先给出饿汉模式的实现 正解: 在实例化m_instance 变量时,直接调用类的构造函数。顾名思义,在还未使用变量时,已经对m_instance进行赋值,就像很饥饿
本文向大家介绍C#线程队列用法实例分析,包括了C#线程队列用法实例分析的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了C#线程队列用法。分享给大家供大家参考。具体如下: 希望本文所述对大家的C#程序设计有所帮助。
本文向大家介绍C++ 线程安全信号,包括了C++ 线程安全信号的使用技巧和注意事项,需要的朋友参考一下 示例 C ++ 11 C ++ 11标准保证以同步方式初始化函数作用域对象的初始化。这可以用于通过延迟初始化实现线程安全的单例。