当前位置: 首页 > news >正文

C#线程 ConcurrentQueue安全队列介绍

https://blog.csdn.net/qq_41230604/article/details/126305068

C#线程安全队列ConcurrentQueue
ConcurrentQueue队列是一个高效的线程安全的队列,是Net Framework 4.0,System.Collections.Concurrent命名空间下的一个数据结构。

ConcurrentQueue内部结构:


实现原理
众所周知,在普通的非线程安全队列有两种实现方式:

1.使用数组实现队列。 2.使用链表实现队列。

看看两种方式的优劣:
  .Net Farmework中的普通队列Queue的实现使用了第一种方式,缺点是当队列空间不足会进行扩容,扩容的主要实现是开辟一个原始长度2倍的新数组,然后将原始数组里面的数据复制到新数组中,所以当扩容时就会产生不小的内存开销,在并发的环境中对性能的影响不可小视。当然在调用Queue的构造函数时可以指定默认空间的大小,但是一般情况下数据量是不可预测的,选大了会照成空间浪费,选小了会有复制内存的开销,而且队列扩容以后需要显示调用TrimToSize()方法才能回收掉不使用的内存空间。

  第二种链表实现方式虽然消除了空间浪费的问题但是又增加了GC的压力,当入队时会分配一个新节点,出队时要对该节点进行废弃,对于大量的出队入队操作时该实现方式性能不高。

  综合以上两种实现方式,在支持多线程并发出队并发入队的情况下,ConcurrentQueue使用了分段存储的概念(如上图所示),ConcurrentQueue分配内存时以段(Segment)为单位,一个段内部含有一个默认长度为32的数组和执行下一个段的指针,有个和Head和Tail指针分别指向了起始段和结束段(这种结构有点像操作系统的段式内存管理和页式内存管理策略)。这种分配内存的实现方式不但减轻的GC的压力而且调用者也不用显示的调用TrimToSize()方法回收内存(在某段内存为空时,会由GC来回收该段内存)。

Segment内部和用数组实现的普通队列相当,只不过对于入队和出队操作使用了原子操作来防止多线程竞争问题,使用随机退让等技术保证活锁等问题,实现机制和ConcurrentStack差别不大,跟多TryAppend的实现细节在源码注释中已经阐述的非常清楚这里就再做不过多的解释。

主要成员函数
入队(EnQueue) 、出队(TryDequeue) 、是否为空(IsEmpty)、获取队列内元素数量(Count)。

void Enqueue(T item) 入队函数

public void Enqueue(T item)
{
    Spinwait spin = new Spinwait();
    while (true)
    {
        Segment tail =m_tail;
        if ( tail .TryAppend(item))
        return;
        spin.SpinOnce();
    }
}

如上代码所示,入队操作是在尾部的段中进行,当数据进入段内失败时会先进行一个回退操作然后再不断尝试直到成功,这里失败的原因(tail.Append(item)返回false)只有一个就是当该段内的空间不够时正在分配新的段,这段时间内会进入该段的元素会失败。
当队列已满时会自动增加队列容量。

bool TryDequeue(T result) 出队函数
尝试出队函数,如果当前队列为空,返回false,否则返回队列的第一个元素。

public bool TryDequeue(out T result)
{
    while (!IsEmpty)
    {
        Segment head = m_head;
        if (head.TryRemove(out result))
            return true;
    }

    result = default(T);
    return false;
}

如上代码所示,出队失败时返回false 而不是像入队一样进行回退操作,因为出队失败的原因只有一个就是当队列内所有段的元素为空时,所以出队设计成了返回bool值的函数。

bool TryPeek(T * result)
跟TryDequeue()方法相似,但不删除队列中的元素。

int Count()
返回当前队列中元素的个数。

找到头节点的low的位置和尾节点的high的位置,由于每个段内记录了当前段在队列中的索引,所以很容易求出整个队列中元素的数量。

跟ConcurrentStack一样 微软官方文档和注释中也说明:判断队列是否为空要使用IsEmpty属性而不是判断Count == 0 原因在于GetHeadTailPositions在大量数据入队和出队的过程中寻找头尾节点的位置是比较耗时的操作,要不断循环确定头尾节点的位置,所以判断队列是否为空还是使用IsEmpty属性。

bool IsEmpty()
判定当前队列为空。

整个判断主要有三种情况:
1.头节点(段)不为空返回false
2.头节点为空而且下一个节点也为空返回true
3.头节点为空而且下一个节点不为空返回false,这种情况说明队列正在扩容,所以要自选等待扩容完毕时再次进行判断

void Reset()
清空并复位队列。

相关文章:

  • 前端技术探秘-Nodejs的CommonJS规范实现原理 | 京东物流技术团队
  • 【TIDB】TiDB认证考试PTCA 练习题 题库
  • 【javaWeb】HTTP协议
  • Spring事件注解@EventListener【观察】
  • 【面试HOT200】滑动窗口篇
  • Centos8部署LNMP架构
  • php使用Session实现简单购物车功能
  • git commmit type格式
  • 小程序静默授权获取unionid
  • Jenkins 配置节点交换内存
  • java:简单入门定时任务的几种方式Timer、Quartz、Spring Task
  • 隐式类型转化
  • 使用Arrays.asList与不使用的区别
  • 2-Python与设计模式--工厂类相关模式
  • docker通过挂载conf文件启动redis
  • 【css3】浏览器内核及其兼容性
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • iBatis和MyBatis在使用ResultMap对应关系时的区别
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • Java多线程(4):使用线程池执行定时任务
  • Js基础——数据类型之Null和Undefined
  • Redis中的lru算法实现
  • Vue UI框架库开发介绍
  • 工作中总结前端开发流程--vue项目
  • 如何实现 font-size 的响应式
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • ​Distil-Whisper:比Whisper快6倍,体积小50%的语音识别模型
  • ​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​
  • # Pytorch 中可以直接调用的Loss Functions总结:
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (52)只出现一次的数字III
  • (十三)Flask之特殊装饰器详解
  • (四)docker:为mysql和java jar运行环境创建同一网络,容器互联
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • (转载)利用webkit抓取动态网页和链接
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .NET 分布式技术比较
  • .Net各种迷惑命名解释
  • .NET下的多线程编程—1-线程机制概述
  • .project文件
  • /etc/skel 目录作用
  • ??eclipse的安装配置问题!??
  • @column注解_MyBatis注解开发 -MyBatis(15)
  • @ConditionalOnProperty注解使用说明
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节
  • [ vulhub漏洞复现篇 ] Jetty WEB-INF 文件读取复现CVE-2021-34429
  • [ 常用工具篇 ] AntSword 蚁剑安装及使用详解
  • [20190113]四校联考
  • [BZOJ] 3262: 陌上花开
  • [GN] Vue3.2 快速上手 ---- 核心语法2
  • [LeetCode][LCR178]训练计划 VI——使用位运算寻找数组中不同的数字
  • [LeetCode]Multiply Strings
  • [LeetCode]剑指 Offer 40. 最小的k个数
  • [Linux基础开发工具---vim]关于vim的介绍、vim如何配置及vim的基本操作方法
  • [Manacher]【学习笔记】