.Net多线程编程—并发集合
并发集合
1 为什么使用并发集合?
原因主要有以下几点:
- System.Collections和System.Collections.Generic名称空间中所提供的经典列表、集合和数组都不是线程安全的,若无同步机制,他们不适合于接受并发的指令来添加和删除元素。
- 在并发代码中使用上述经典集合需要复杂的同步管理,使用起来很不方便。
- 使用复杂的同步机制会大大降低性能。
- NET Framework 4所提供的新的集合尽可能地减少需要使用锁的次数。这些新的集合通过使用比较并交换(compare-and-swap,CAS)指令和内存屏障,避免使用互斥的重量级锁。这对性能有保障。
注意:
与经典集合相比,并发集合会有更大的开销,因此在串行代码中使用并发集合无意义,只会增加额外的开销且运行速度比访问经典集合慢。
2 并发集合
1)ConcurrentQueue:线程安全的先进先出 (FIFO) 集合
主要方法:
- Enqueue(T item);将对象添加到集合结尾。
- TryDequeue(out T result); 尝试移除并返回位于集合开始处的对象,返回值表示操作是否成功。
- TryPeek(out T result);尝试返回集合开始处的对象,但不将其移除,返回值表示操作是否成功。
说明:
- ConcurrentQueue是完全无锁的,但当CAS操作失败且面临资源争用时,它可能会自旋并且重试操作。
- ConcurrentQueue是FIFO集合,某些和出入顺序无关的场合,尽量不要用ConcurrentQueue。
2)ConcurrentStack:线程安全的后进先出 (LIFO) 集合
主要方法及属性:
- Push(T item);将对象插入集合的顶部。
- TryPop(out T result);尝试弹出并返回集合顶部的对象,返回值表示操作是否成功。
- TryPeek(out T result);尝试返回集合开始处的对象,但不将其移除,返回值表示操作是否成功。
- IsEmpty { get; }指示集合是否为空。
- PushRange(T[] items);将多个对象插入集合的顶部。
- TryPopRange(T[] items);弹出顶部多个元素,返回结果为弹出元素个数。
说明:
- 与ConcurrentQueue相似地,ConcurrentStack完全无锁的,但当CAS操作失败且面临资源争用时,它可能会自旋并且重试操作。
- 获取集合是否包含元素使用IsEmpty属性,而不是通过判断Count属性是否大于零。调用Count比调用IsEmpty开销大。
- 使用PushRange(T[] items)和TryPopRange(T[] items)时注意缓冲引起的额外开销和额外的内存消耗。
3) ConcurrentBag:元素可重复的无序集合
主要方法及属性:
- TryPeek(out T result);尝试从集合返回一个对象,但不移除该对象,返回值表示是否成功获得该对象。
- TryTake(out T result);尝试从集合返回一个对象并移除该对象,返回值表示是否成功获得该对象。
- Add(T item);将对象添加到集合中。
- IsEmpty { get; }解释同ConcurrentStack
说明:
- ConcurrentBag为每一个访问集合的线程维护了一个本地队列,在可能的情况下,它会以无锁的方式访问本地队列。
- ConcurrentBag在同一个线程添加和删除元素的场合下效率非常高。
- 因为ConcurrentBag有时会需要锁,在生产者线程和消费者线程完全分开的场景下效率非常低。
- ConcurrentBag调用IsEmpty的开销非常大,因为这需要临时获得这个无序组的所有锁。
4)BlockingCollection:实现
System.Collections.Concurrent.IProducerConsumerCollection<T> 的线程安全集合,提供阻塞和限制功能
主要方法及属性:
- BlockingCollection(int boundedCapacity);boundedCapacity表示集合限制大小。
- CompleteAdding();将BlockingCollection实例标记为不再接受任何添加。
- IsCompleted { get; }此集合是否已标记为已完成添加并且为空。
- GetConsumingEnumerable();从集合中移除并返回移除的元素
- Add(T item);添加元素到集合。
- TryTake(T item, int millisecondsTimeout, CancellationToken cancellationToken);
说明:
- 使用BlockingCollection()构造函数实例化BlockingCollection,意味着不设置boundedCapacity,那么boundedCapacity为默认值: int.MaxValue。
- 限界:使用BlockingCollection(int boundedCapacity),设置boundedCapacity的值,当集合容量达到这个值得时候,向BlockingCollection添加元素的线程将会被阻塞,直到有元素被删除。
限界功能可控制内存中集合最大大小,这对于需要处理大量元素的时候非常有用。
- 默认情况下,BlockingCollection封装了一个ConcurrentQueue。可以在构造函数中指定一个实现了IProducerConsumerCollection接口的并发集合,包括:ConcurrentStack、ConcurrentBag。
- 使用此集合包含易于无限制等待的风险,所以使用TryTake更加,因为TryTake提供了超时控制,指定的时间内可以从集合中移除某个项,则为 true;否则为 false。
5)ConcurrentDictionary:可由多个线程同时访问的键值对的线程安全集合。
主要方法
- AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory);如果指定的键尚不存在,则将键/值对添加到 字典中;如果指定的键已存在,则更新字典中的键/值对。
- GetOrAdd(TKey key, TValue value);如果指定的键尚不存在,则将键/值对添加到字典中。
- TryRemove(TKey key, out TValue value);尝试从字典中移除并返回具有指定键的值。
- TryUpdate(TKey key, TValue newValue, TValue comparisonValue);将指定键的现有值与指定值进行比较,如果相等,则用第三个值更新该键。
说明:
- ConcurrentDictionary对于读操作是完全无锁的。当多个任务或线程向其中添加元素或修改数据的时候,ConcurrentDictionary使用细粒度的锁。使用细粒度的锁只会锁定真正需要锁定的部分,而不是整个字典。
6)IProducerConsumerCollection:定义供生产者/消费者用来操作线程安全集合的方法。 此接口提供一个统一的表示(为生产者/消费者集合),从而更高级别抽象如 System.Collections.Concurrent.BlockingCollection<T>可以使用集合作为基础的存储机制。
3.常用模式
1)并行的生产者-消费者模式
定义:
生成者和消费者是此模式中的两类对象模型,消费者依赖于生产者的结果,生产者生成结果的同时,消费者使用结果。
图1 并行的生产者-消费者模式
说明:
- 并发集合用在此模式下非常合适,因为并发集合支持此模式中对象的并行操作。
- 若不使用并发集合,那么就要加入同步机制,从而使程序变得比较复杂,难于维护和理解,同时大大降低性能。
- 上图为生产者消费者模式示意图,纵轴为时间轴,生成者与消费者的并不在一条时间线上,但二者有交叉,意在表明生成者先产生结果,而后消费者才真正使用了生成者产生的数据。
2)流水线模式
定义:
流水线由多个阶段构成,每个阶段由一系列的生产者和消费者构成。一般来讲前一个阶段是后一个阶段的生成者;依靠相邻两个阶段之间的缓冲区队列,每个阶段可以并发执行。
图2 并行的流水线模式
说明:
- 常使用BlockingCollection<T>作为缓冲罐区队列。
- 流水线的速度近似等于流水线最慢阶段的速度。
- 上图为流水线模式示意图,前一阶段为后一阶段的生成者,这里展示了最为简单和基本的流水线模式,更复杂的模式可以认为是每个阶段都包括了对数据更多的处理过程。
4 使用方式
仅以ConcurrentBag和BlockingCollection为例,其他的并发集合与之相似。
ConcurrentBag
List<string> list = ......
ConcurrentBag<string> bags = new ConcurrentBag<string>();
Parallel.ForEach(list, (item) =>
{
//对list中的每个元素进行处理然后,加入bags中
bags.Add(itemAfter);
});
BlockingCollection—生产者消费者模式
public static void Execute()
{
//调用Invoke,使得生产者任务和消费者任务并行执行
//Producer方法和Customer方法在Invoke中的参数顺序任意,不论何种顺序都会获得正确的结果
Parallel.Invoke(()=>Customer(),()=>Producer());
Console.WriteLine(string.Join(",",customerColl));
} //生产者集合
private static BlockingCollection<int> producerColl = new BlockingCollection<int>();
//消费者集合
private static BlockingCollection<string> customerColl = new BlockingCollection<string>(); public static void Producer()
{
//循环将数据加入生成者集合
for (int i = ; i < ; i++)
{
producerColl.Add(i);
} //设置信号,表明不在向生产者集合中加入新数据
//可以设置更加复杂的通知形式,比如数据量达到一定值且其中的数据满足某一条件时就设置完成添加
producerColl.CompleteAdding();
} public static void Customer()
{
//调用IsCompleted方法,判断生产者集合是否在添加数据,是否还有未"消费"的数据
//注意不要使用IsAddingCompleted,IsAddingCompleted只表明集合标记为已完成添加,而不能说明其为空
//而IsCompleted为ture时,那么IsAddingCompleted为ture且集合为空
while (!producerColl.IsCompleted)
{
//调用Take或TryTake "消费"数据,消费一个,移除一个
//TryAdd的好处是提供超时机制
customerColl.Add(string.Format("消费:{0}", producerColl.Take()));
}
}
-----------------------------------------------------------------------------------------
转载与引用请注明出处。
时间仓促,水平有限,如有不当之处,欢迎指正。
.Net多线程编程—并发集合的更多相关文章
- 转载 .Net多线程编程—并发集合 https://www.cnblogs.com/hdwgxz/p/6258014.html
集合 1 为什么使用并发集合? 原因主要有以下几点: System.Collections和System.Collections.Generic名称空间中所提供的经典列表.集合和数组都不是线程安全的, ...
- C#并行编程-并发集合
菜鸟学习并行编程,参考<C#并行编程高级教程.PDF>,如有错误,欢迎指正. 目录 C#并行编程-相关概念 C#并行编程-Parallel C#并行编程-Task C#并行编程-并发集合 ...
- java多线程中并发集合和同步集合有哪些?区别是什么?
java多线程中并发集合和同步集合有哪些? hashmap 是非同步的,故在多线程中是线程不安全的,不过也可以使用 同步类来进行包装: 包装类Collections.synchronizedMap() ...
- Java多线程编程——并发编程原理(分布式环境中并发问题)
在分布式环境中,处理并发问题就没办法通过操作系统和JVM的工具来解决,那么在分布式环境中,可以采取一下策略和方式来处理: 避免并发 时间戳 串行化 数据库 行锁 统一触发途径 避免并发 在分布式环境中 ...
- 已看1.熟练的使用Java语言进行面向对象程序设计,有良好的编程习惯,熟悉常用的Java API,包括集合框架、多线程(并发编程)、I/O(NIO)、Socket、JDBC、XML、反射等。[泛型]\
1.熟练的使用Java语言进行面向对象程序设计,有良好的编程习惯,熟悉常用的Java API,包括集合框架.多线程(并发编程).I/O(NIO).Socket.JDBC.XML.反射等.[泛型]\1* ...
- Java多线程与并发编程学习
一.线程三大特性 多线程有三大特性,原子性.可见性.有序性 1.1 什么是原子性 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行.一个很经典的例子就是银行账户转账 ...
- Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) JAVA日志的前世今生 .NET MVC采用SignalR更新在线用户数 C#多线程编程系列(五)- 使用任务并行库 C#多线程编程系列(三)- 线程同步 C#多线程编程系列(二)- 线程基础 C#多线程编程系列(一)- 简介
Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) 一.前言 由于本篇文章较长,所以下面给出内容目录方便跳转阅读,当然也可以用博客页面最右侧的文章目录导航栏进行跳转查阅. 一.前言 ...
- 《Clojure编程》笔记 第4章 多线程和并发
目录 背景简述 第4章 多线程和并发 4.0 我的问题 4.1 术语 4.1.1 一个必须要先确定的思考基础 4.2 计算在时间和空间内的转换 4.2.1 delay 4.2.2 future 4.2 ...
- Java多线程 阻塞队列和并发集合
转载:大关的博客 Java多线程 阻塞队列和并发集合 本章主要探讨在多线程程序中与集合相关的内容.在多线程程序中,如果使用普通集合往往会造成数据错误,甚至造成程序崩溃.Java为多线程专门提供了特有的 ...
随机推荐
- Unity3d入门 - 关于unity工具的熟悉
上周由于工作内容较多,花在unity上学习的时间不多,但总归还是学习了一些东西,内容如下: .1 根据相关的教程在mac上安装了unity. .2 学习了unity的主要的工具分布和对应工具的相关的功 ...
- Java MyBatis 插入数据库返回主键
最近在搞一个电商系统中由于业务需求,需要在插入一条产品信息后返回产品Id,刚开始遇到一些坑,这里做下笔记,以防今后忘记. 类似下面这段代码一样获取插入后的主键 User user = new User ...
- Ignite性能测试以及对redis的对比
测试方法 为了对Ignite做一个基本了解,做了一个性能测试,测试方法也比较简单主要是针对client模式,因为这种方法和使用redis的方式特别像.测试方法很简单主要是下面几点: 不作参数优化,默认 ...
- 2015 西雅图微软总部MVP峰会记录
2015 西雅图微软总部MVP峰会记录 今年决定参加微软MVP全球峰会,在出发之前本人就已经写这篇博客,希望将本次会议原汁原味奉献给大家 因为这次是本人第一次写会议记录,写得不好的地方希望各位园友见谅 ...
- app开发外包注意事项,2017最新资讯
我们见过很多创业者,栽在这app外包上.很多创业者对于app外包这件事情不是特别重视,以为将事情交给app外包公司就完事了,实际上不是的.无论是从选择app外包公司还是签订合同.售后维护等各方面都有许 ...
- 游戏服务器菜鸟之C#初探一游戏服务
本人80后程序猿一枚,原来搞过C++/Java/C#,因为工作原因最后选择一直从事C#开发,因为读书时候对游戏一直比较感兴趣,机缘巧合公司做一个手游的项目,我就开始游戏服务器的折腾之旅. 游戏的构架是 ...
- ASP.NET Core应用针对静态文件请求的处理[5]: DefaultFilesMiddleware中间件如何显示默认页面
DefaultFilesMiddleware中间件的目的在于将目标目录下的默认文件作为响应内容.我们知道,如果直接请求的就是这个默认文件,那么前面介绍的StaticFileMiddleware中间件会 ...
- ASP.NET Core应用针对静态文件请求的处理[4]: DirectoryBrowserMiddleware中间件如何呈现目录结构
和StaticFileMiddleware中间件一样,DirectoryBrowserMiddleware中间本质上还是定义了一个请求地址与某个物理目录之间的映射关系,而目标目录体现为一个FilePr ...
- 读书笔记汇总 - SQL必知必会(第4版)
本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...
- 通过微信小程序看前端
前言 2016年9月22日凌晨,微信官方通过“微信公开课”公众号发布了关于微信小程序(微信应用号)的内测通知.整个朋友圈瞬间便像炸开了锅似的,各种揣测.介绍性文章在一夜里诞生.而真正收到内测邀请的公众 ...