C#的变迁史07 - C# 4.0 之线程安全集合篇

  作为多线程和并行计算不得不考虑的问题就是临界资源的访问问题,解决临界资源的访问通常是加锁或者是使用信号量,这个大家应该很熟悉了。

  而集合作为一种重要的临界资源,通用性更广,为了让大家更安全的使用它们,微软为我们带来了强大的并行集合:System.Collections.Concurrent里面的各位仁兄们。

  首先,咱们从一个经典的问题谈起。

生产者消费者问题

  这个问题是最为经典的多线程应用问题,简单的表述这个问题就是:有一个或多个线程(生产者线程)产生一些数据,同时,还有一个或者多个线程(消费者线程)要取出这些数据并执行一些相应的工作。如下图所示:

  下面就是使用程序去描述这个问题了。

  最直接的想法可能是这样:

复制代码

static void Main(string[] args)
{int count = 0;// 临界资源区var queue = new Queue<string>();// 生产者线程Task.Factory.StartNew(() =>{while (true){queue.Enqueue("value" + count);count++;}});// 消费者线程1Task.Factory.StartNew(() =>{while (true){if (queue.Count > 0){string value = queue.Dequeue();Console.WriteLine("Worker 1: " + value);}}});// 消费者线程2Task.Factory.StartNew(() =>{while (true){if (queue.Count > 0){string value = queue.Dequeue();Console.WriteLine("Worker 2: " + value);}}});Thread.Sleep(50000);
}

复制代码

  使用Queue<string>模拟了一个简单的资源池,一个生产者放数据,两个消费者消费数据。上面这个程序运行以后会产生异常,异常的原因很简单,当某个时刻,第一个消费者判断queue.Count > 0为true时,就会到Queue中取数据,但是这个时候数据可能会被第二个消费者拿走了,因为第二个消费者也判断出此时有数据可取。这是一个简单的临界资源线程安全问题。

  知道问题了,那么如何解决呢?
  第一种方案是加锁,这个方案是可行的,很多时候我们也是这么做的,包括微软早期实现线程安全的ArrayList和Hashtable内部(Synchronized方法)也是这么实现的。这个方案适用于只有少量的消费者,并且每个消费者都会执行大量操作的时候,这时lock并没什么太大问题,但是,如果是大批量短小精悍的消费者存在的话,lock会严重影响代码的执行效率。
  第二种方案就是我们直接用新的线程安全的集合区解决这个问题。新的线程安全的这些集合内部不再使用lock机制这种比较低效的方式去实现线程安全,而是转而使用SpinWait和Interlocked等机制,间接实现了线程安全,这种方式的效率要高于使用lock的方式。看一下实现代码:

复制代码

var queue = new ConcurrentQueue<string>();
Task.Factory.StartNew(() =>
{while (true){queue.Enqueue("value" + count);count++;}
});Task.Factory.StartNew(() =>
{while (true){string value;if (queue.TryDequeue(out value)){Console.WriteLine("Worker 1: " + value);}}
});Task.Factory.StartNew(() =>
{while (true){string value;if (queue.TryDequeue(out value)){Console.WriteLine("Worker 2: " + value);}}
});

复制代码

  执行这段代码,可以工作,但是有点不太优雅,能不能不要去判断集合是否为空?集合当自己没有元素的时候自己Block一下可以吗?答案当然是可以的,使用BlockingCollection即可:

复制代码

var blockingCollection = new BlockingCollection<string>();
Task.Factory.StartNew(() =>
{while (true){blockingCollection.Add("value" + count);count++;}
});Task.Factory.StartNew(() =>
{while (true){Console.WriteLine("Worker 1: " + blockingCollection.Take());}
});Task.Factory.StartNew(() =>
{while (true){Console.WriteLine("Worker 2: " + blockingCollection.Take());}
});

复制代码

  BlockingCollection集合是一个拥有阻塞功能的集合,它就是完成了经典生产者消费者的算法功能。它没有实现底层的存储结构,而是使用了实现IProducerConsumerCollection接口的几个集合作为底层的数据结构,例如ConcurrentBag, ConcurrentStack或者是ConcurrentQueue。你可以在构造BlockingCollection实例的时候传入这个参数,如果不指定的话,则默认使用ConcurrentQueue作为存储结构。

  而对于生产者来说,只需要通过调用其Add方法放数据,消费者只需要调用Take方法来取数据就可以了。
  当然了上面的消费者代码中还有一点是让人不爽的,那就是while语句,可以更优雅一点吗?答案还是肯定的:

复制代码

Task.Factory.StartNew(() =>
{foreach (string value in blockingCollection.GetConsumingEnumerable()){Console.WriteLine("Worker 1: " + value);}
});

复制代码

  GetConsumingEnumerable()方法是关键,这个方法会遍历集合取出数据,一旦发现集合空了,则阻塞自己,直到集合中又有元素了再开始遍历,神奇吧。

  好了,到此完美了解决了生产者消费者问题。然而通常来说,还有两个问题我们有时需要去控制:

第一个问题:控制集合中数据的最大数量。

  这个问题由BlockingCollection构造函数解决,构造该对象实例的时候,构造函数中的BoundedCapacity决定了集合最大的可容纳数据数量,这个比较简单,不多说了。

第二个问题:何时停止的问题。

  这个问题由CompleteAdding和IsCompleted两个配合解决。
  CompleteAdding方法是直接不允许任何元素被加入集合;当使用了CompleteAdding方法后且集合内没有元素的时候,另一个属性IsCompleted此时会为True,这个属性可以用来判断是否当前集合内的所有元素都被处理完。看一下生产者修改后的代码:

复制代码

Task.Factory.StartNew(() =>
{for (int count = 0; count < 10; count++){blockingCollection.Add("value" + count);}blockingCollection.CompleteAdding();
});

复制代码

  当使用了CompleteAdding方法后,对象停止往集合中添加数据,这时如果是使用GetConsumingEnumerable枚举的,那么这种枚举会自然结束,不会再Block住集合,这种方式最优雅,也是推荐的写法。但是如果是使用TryTake访问元素的,则需要使用IsCompleted判断一下,因为这个时候使用TryTake会抛InvalidOperationException异常。

看一下最终的代码形式:

复制代码

static void Main(string[] args)
{var blockingCollection = new BlockingCollection<string>();var producer = Task.Factory.StartNew(() =>{for (int count = 0; count < 10; count++){blockingCollection.Add("value" + count);Thread.Sleep(300);}blockingCollection.CompleteAdding();});var consumer1 = Task.Factory.StartNew(() =>{foreach (string value in blockingCollection.GetConsumingEnumerable()){Console.WriteLine("Worker 1: " + value);}});var consumer2 = Task.Factory.StartNew(() =>{foreach (string value in blockingCollection.GetConsumingEnumerable()){Console.WriteLine("Worker 2: " + value);}});Task.WaitAll(producer, consumer1, consumer2);
}

复制代码

BlockingCollection的枚举

  此外,需要注意BlockingCollection有两种枚举方法,首先BlockingCollection本身继承自IEnumerable<T>,所以它自己就可以被foreach枚举,首先BlockingCollection包装了一个线程安全集合,那么它自己也是线程安全的,而当多个线程在同时修改或访问线程安全容器时,BlockingCollection自己作为IEnumerable会返回一个一定时间内的集合片段,也就是只会枚举在那个时间点上内部集合的元素。使用这种方式枚举的时候,不会有Block效果。
  另外一种方式就是我们上面使用的GetConsumingEnumerable方式的枚举,这种方式会有Block效果,直到CompleteAdding被调用为止。

  最后提一下实现IProducerConsumerCollection接口的几个集合:ConcurrentBag(线程安全的无序的元素集合), ConcurrentStack(线程安全的堆栈)和ConcurrentQueue(线程安全的队列)。这些都很简单,功能与非线程安全的那些集合都一样,只不多是多了TryXXX方法,多线程环境下使用这些方法就好了,其他就不多说了。

  到此生产者和消费者这个经典的问题告一段落了。

  System.Collections.Concurrent下面的集合除了解决生产者消费者问题外,还有一些与多线程相关的集合,例如:

1. ConcurrentDictionary,这个是键/值对字典的线程安全实现,这个类在原来的基础上也添加了一下新的方法,例如:AddOrUpdate,GetOrAdd,TryXXX等等,都很容易理解。

2. 各种Partitioner 类,提供针对数组、列表和可枚举项的常见分区策略。

  若要对数据源操作进行并行化,其中一个必要步骤是将源分区为可由多个线程同时访问的多个部分。 PLINQ 和任务并行库 (TPL) 提供了默认的分区程序,当编写并行查询或ForEach循环时,默认的分区程序以透明方式工作。 但是毫无疑问,对于一些复杂的情况,我们是可以插入自己的分区程序的,这就是微软为我们提供的各种Partitioner类,这个不多说了,感兴趣的同学请自己参考一下MSDN。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PWN-PRACTICE-BUUCTF-1

PWN-PRACTICE-BUUCTF-1test_your_ncripwarmup_csaw_2016ciscn_2019_n_1test_your_nc 附件的main函数直接system("/bin/sh")&#xff0c;nc直接连即可cat flag rip main函数中&#xff0c;gets函数读取一行会造成栈溢出 构造payload覆盖rip&#xff0c;使得return…

C#的变迁史08 - C# 5.0 之并行编程总结篇

C# 5.0 搭载于.NET 4.5和VS2012之上。 同步操作既简单又方便&#xff0c;我们平时都用它。但是对于某些情况&#xff0c;使用同步代码会严重影响程序的可响应性&#xff0c;通常来说就是影响程序性能。这些情况下&#xff0c;我们通常是采用异步编程来完成功能&#xff0c;这在…

REVERSE-PRACTICE-CTFSHOW-1

REVERSE-PRACTICE-CTFSHOW-1逆向签到题re2逆向4逆向5逆向签到题 ida打开即可得到明文flag re2 附件是一个加密过的flag文本和勒索病毒exe 运行程序&#xff0c;输入1&#xff0c;回车&#xff0c;直接退出&#xff0c;ida分析 选项1的逻辑为&#xff0c;打开flag.txt和enfl…

C#的变迁史09 - C# 5.0 之调用信息增强篇

Caller Information CallerInformation是一个简单的新特性&#xff0c;包括三个新引入的Attribute&#xff0c;使用它们可以用来获取方法调用者的信息&#xff0c; 这三个Attribute在System.Runtime.CompilerServices命名空间下&#xff0c;分别叫做CallerMemberNameAttribute&…

REVERSE-PRACTICE-CTFSHOW-2

REVERSE-PRACTICE-CTFSHOW-2re3红包题 武穆遗书数学不及格flag白给re3 main函数&#xff0c;分析可知&#xff0c;将输入的字符串按十六进制转成数字&#xff0c;写到v5&#xff0c;赋给v17[6] 当i等于6时&#xff0c;v16会加上输入的值&#xff0c;然后进入循环&#xff0c;最…

C#的变迁史10 - C# 5.0 之其他增强篇

1. 内置zip压缩与解压   Zip是最为常用的文件压缩格式之一&#xff0c;也被几乎所有操作系统支持。在之前&#xff0c;使用程序去进行zip压缩和解压要靠第三方组件去支持&#xff0c;这一点在.NET4.5中已有所改观&#xff0c;Zip压缩和解压功能已经内置于框架本身。这个功能使…

REVERSE-PRACTICE-CTFSHOW-3

REVERSE-PRACTICE-CTFSHOW-3签退神光签到baby_gay签退 .pyc文件&#xff0c;uncompyle6反编译&#xff0c;得到python源码&#xff0c;分析写在源码注释中 先变表base64&#xff0c;再凯撒加密&#xff0c;向后移动2位 import string c_charset string.ascii_uppercase str…

REVERSE-PRACTICE-CTFSHOW-4

REVERSE-PRACTICE-CTFSHOW-4encodeEasyBJD hamburger competitionJustREencode elf文件&#xff0c;upx脱壳&#xff0c;ida分析 交叉引用字符串"Please input your flag:"&#xff0c;来到sub_804887C函数 输入经过三次变换&#xff0c;先是变表base64&#xff0c;…

CSS 基础框盒模型介绍

当对一个文档进行布局&#xff08;lay out&#xff09;的时候&#xff0c;浏览器的渲染引擎会根据标准之一的 CSS 基础框盒模型&#xff08;CSS basic box model&#xff09;&#xff0c;将所有元素表示为一个个矩形的盒子&#xff08;box&#xff09;。CSS 决定这些盒子的大小…

REVERSE-PRACTICE-CTFSHOW-5

REVERSE-PRACTICE-CTFSHOW-5re2_归心Mud[吃鸡杯]ezmore[吃鸡杯]有手就行re2_归心 exe程序&#xff0c;运行后要求输入flag&#xff0c;ida分析 函数窗没找到主逻辑函数&#xff0c;shiftF12看字符串窗口 发现有java/lang/String&#xff0c;com/exe4j/runtime/WinLauncher等字…

PWN-PRACTICE-BUUCTF-2

PWN-PRACTICE-BUUCTF-2pwn1_sctf_2016jarvisoj_level0ciscn_2019_c_1[第五空间2019 决赛]PWN5pwn1_sctf_2016 main函数中执行vuln函数 fgets限制了输入的长度&#xff0c;不足以构成栈溢出 通过将输入中的字符"I"替换成"you"&#xff0c;增加长度&#xf…

PWN-PRACTICE-BUUCTF-3

PWN-PRACTICE-BUUCTF-3[OGeek2019]babyropciscn_2019_n_8get_started_3dsctf_2016jarvisoj_level2[OGeek2019]babyrop 简单的ret2libc&#xff0c;构造rop main函数中读取一个随机数到buf中&#xff0c;传入sub_804871F 用"\x00"来绕过strlen和strncmp&#xff0c;b…

c#中常用集合类和集合接口之接口系列【转】

常用集合接口系列&#xff1a;http://www.cnblogs.com/fengxiaojiu/p/7997704.html 常用集合类系列&#xff1a;http://www.cnblogs.com/fengxiaojiu/p/7997541.html 大多数集合都在System.Collections&#xff0c;System.Collections.Generic两个命名空间。其中System.Colle…

PWN-PRACTICE-BUUCTF-4

PWN-PRACTICE-BUUCTF-4ciscn_2019_en_2bjdctf_2020_babystacknot_the_same_3dsctf_2016[HarekazeCTF2019]baby_ropciscn_2019_en_2 这题和ciscn_2019_c_1一模一样 栈溢出ret2libc&#xff0c;encrypt函数里的异或运算不用管 from pwn import * context.log_level"debug&…

PWN-PRACTICE-BUUCTF-5

PWN-PRACTICE-BUUCTF-5jarvisoj_level2_x64ciscn_2019_n_5others_shellcodeciscn_2019_ne_5jarvisoj_level2_x64 这题和[HarekazeCTF2019]baby_rop几乎一模一样 from pwn import * #context.log_level"debug" ioremote(node4.buuoj.cn,27023) elfELF(./level2_x64)…

Scrum敏捷开发沉思录

计算机科学的诞生&#xff0c;是世人为了用数字手段解决实际生活中的问题。随着时代的发展&#xff0c;技术的进步&#xff0c;人们对于现实世界中的问题理解越来越深刻&#xff0c;描述也越来越抽象&#xff0c;于是对计算机软件的需求也越来越高&#xff0c;越来越复杂&#…

PWN-PRACTICE-BUUCTF-6

PWN-PRACTICE-BUUCTF-6铁人三项(第五赛区)_2018_ropbjdctf_2020_babyropbabyheap_0ctf_2017pwn2_sctf_2016铁人三项(第五赛区)_2018_rop vulnerable_function函数中read构成栈溢出&#xff0c;ret2libc from pwn import * context.log_level"debug" ioremote(node4…

PWN-PRACTICE-BUUCTF-7

PWN-PRACTICE-BUUCTF-7jarvisoj_fmciscn_2019_s_3SROP解法ret2csu解法bjdctf_2020_babystack2[HarekazeCTF2019]baby_rop2jarvisoj_fm 格式化字符串漏洞&#xff0c;可以测出我们的输入在栈上的偏移为11 自己构造或者使用fmtstr_payload构造payload均可&#xff0c;目标是让x4…

Axure教程 axure新手入门基础(3) 简单易上手

(三)Axure rp元件的触发事件 l OnClick(点击时): 鼠标点击事件&#xff0c;除了动态面板的所有的其他元件的点击时触发。比如点击按钮。 l OnMouseEnter(鼠标移入时): 鼠标进入到某个元件范围时触发&#xff0c;比如当鼠标移到某张图片时显示该图片的介绍。 l OnMouseOut(鼠标移…

使用Nuget 安装指定版本package或者更新package到指定版本

最近在琢磨MVC和EntityFramework,使用的VS是2013版的&#xff0c;在Nuget的GUI界面下安装了EntityFramework(默认安装最新版的&#xff0c;怎么安装指定版本还没找到),按照MVC的示例项目MusicStore逐步做的过程中发现MVC4不支持EntityFramwok 6。 尝试去更新MVC版本各种失败&a…