C# 队列Queue,ConcurrentQueue,BlockingCollection 并发控制lock,Monitor,信号量Semaphore
什么是队列?
我想学习过数据结构应该很清楚,如果没有仔细了解,只要记住队列是一个先进先出的列表即可,列表中可以是线程,可以是预备执行的函数的入口,可以是地址,可以是数据,在C#中,Queue<T> 类可以实现队列,这一个类可以简单的让我们完成数据的插入和获取,可以在便利性这一块十分出众的。
Queue队列就是先进先出。它并没有实现 IList,ICollection。所以它不能按索引访问元素,不能使用Add和Remove。下面是 Queue的一些方法和属性
Enqueue():在队列的末端添加元素
Dequeue():在队列的头部读取和删除一个元素,注意,这里读取元素的同时也删除了这个元素。如果队列中不再有任何元素。就抛出异常
我们将建立一个存储String数据的队列,为了实用,队列能够用多线程的方式来插入,后台将有一个长时间运行的线程来不断将数据从队列取出并执行我们编写的内容。因此需要规划多线程之间资源冲突如何避免的措施
使用Queue类的Enqueue函数来添加,这里定义一个Enqueue函数来添加数据到队列,可以看到有一个object类的锁,它的作用就是防止冲突
当读取数据时候,上锁,防止此时写入数据,当线程读取到-1时候,线程退出程序结束。
微软给我们所提供的这些源码:
- 队列 Queue ;
- 泛型队列 Queue<T>;
- 阻塞泛型集合 BlockingCollection<T>
- 以及微软强大的并行库中的并发泛型队列 ConcurrentQueue<T>
我们着重看一下泛型队列和并发泛型队列
ConcurrentQueue是一个先进先出的队列,常用的有以下几个方法:
- Enqueue(T item) 将对象添加到队列的结尾处
- TryDequeue(out T result) 尝试移除并返回并发队列开头处的对象
- TryPeek(out T result) 尝试返回开头处的对象但不将其移除
Queue适用范围:单线程的队列,Queue是最简单的队列的一个实现,但是对于多线程的情况它就显得捉襟见肘了,因为它不是线程安全的,下面粗略的写一下用法,
Queue<string> messageQueue = new Queue<string>();//创建队列 messageQueue.Enqueue("Hello");//向队列里添加元素 messageQueue.Enqueue("World!") Console.WriteLine(messageQueue.Dequeue());//从队列里取出元素 Console.WriteLine(messageQueue.Dequeue());
ConcurrentQueue很好的解决了Queue的线程不安全的问题,所以它是线程安全,它的应对场景就是多线程了
ConcurrentQueue<string> concurrentQueue = new ConcurrentQueue<string>();//创建实例 concurrentQueue.Enqueue("Hello");//向队列里添加元素 concurrentQueue.Enqueue("World!"); while(concurrentQueue.TryDequeue(out message))//c'v取出元素 { Console.WriteLine(message); }
BlockingCollection属于功能最强大的一个队列,它不仅对于线程是安全的,而且它还可以定义队列中最多可以有多少个元素,应用场景比如秒杀活动等。
BlockingCollection<string> blockingCollection = new BlockingCollection<string>(2);//创建队列实例,其中2就是代表着此队列中元素个数的最大值 blockingCollection.Add("Hello");//添加元素到队列中 blockingCollection.Add("World!"); blockingCollection.Add("Good"); blockingCollection.Add("Evening!"); blockingCollection.take();//从队列中取出元素
总结:单线程用Queue,多线程用ConcurrentQueue,多线程又要限制元素个数用BlockingCollection。
Lock
说到并发控制,我们首先想到的肯定是 lock关键字。
这里要说一下,lock锁的究竟是什么?是lock下面的代码块吗,不,是locker对象。
我们想象一下,locker对象相当于一把门锁(或者钥匙),后面代码块相当于屋里的资源。
哪个线程先控制这把锁,就有权访问代码块,访问完成后再释放权限,下一个线程再进行访问。
注意:如果代码块中的逻辑执行时间很长,那么其他线程也会一直等下去,直到上一个线程执行完毕,释放锁。
写C#代码时,遇到有过程需要排队执行,就使用了lock方法进行锁定,锁定对象为一List<T>数组,在临界区代码段中对该数据进行读取操作。在某些偶然情况下,会发现该数据在锁定代码段以外进行访问时,会抛出一个异常:“源数组长度不足。请检查 srcIndex 和长度以及数组的下限”,此时再执行其他操作就无效了。后查阅资料发现,lock锁定代码中对该数据的操作尚未执行完毕,别处就已在使用该数组,可能是导致异常的一个原因,遂将原来的锁定代码:
Monitor
Monitor是一个静态类(System.Threading.Monitor),功能与lock关键字基本一样,也是加锁,控制并发。
有两个重要的方法:
Monitor.Enter //获取一个锁
Monitor.Exit //释放一个锁
另外几个方法:
public static bool TryEnter(object obj, int millisecondsTimeout) //相比于 public static void Enter(object obj) 方法,多了超时时间设置,如果等待超过一定时间,就不再等待了,另外,只有TryEnter返回值为true时,才能进入代码块。
public static bool Wait(object obj, int millisecondsTimeout) //这个方法在已经获得锁权限的代码块中调用时,或暂时释放锁,等待一定时间后,重新获取锁权限,继续执行Wait后面的代码。 (真想不明怎么会有这种相互礼让的操作)
public static void Pulse(object obj) //这个方法的解释是,通知在等待队列中的线程,锁对象状态改变。 (测试发现,此方法并不会真正改变锁定状态,只是通知的作用)
Semaphore 信号量
System.Threading.Semaphore
lock和Monitor加锁之后,每次只能有一个线程访问临界代码,信号量类似于一个线程池,线程访问之前获取一个信号,访问完成释放信号,只要信号量内有可用信号便可以访问,否则等待。
构造函数:
public Semaphore(int initialCount, int maximumCount) //创建一个信号量,指定初始信号数量和最大信号数量。
几个重要方法:
public int Release //代码注释的意思是:退出信号量,并返回之前的(可用信号)数量。 实际上,除了退出,这个方法每调用一次会增加一个可用信号,但数量达到最大数量时会抛异常。
public int Release(int releaseCount) //和上面的方法类似,上面的方法每次只释放一个信号,这个方法可以指定信号数量。
public virtual bool WaitOne //等待一个可用信号
看下面的示例代码,如果只初始一个信号量, new Semaphore(1, 100),运行结果与lock和Monitor是一样的,两个方法交替执行,如果初始信号量为多个时, new Semaphore(3, 100),执行效率高的方法要占用更多的信号,从而执行更多次。
总结
lock是最常用的并发控制方式,Monitor的功能与lock类似,但使用复杂,非必须不建议使用。
Semaphore,信号量,是一个不错的功能,特定应用场景下非常实用。
ConcurrentQueue 是一个线程安全的队列,在多线程并发环境下使用,可避免由于并发引起的错误。(我们可以使用lock+Queue,实现ConcurrentQueue,自己感兴趣可以试一下)
BlockingCollection 带阻塞功能的 ConcurrentQueue ,没有可用数据的情况下,进入等待状态,防止循环访问,减少CPU资源浪费。
帮助类
public class BlockingQueue
{
private readonly BlockingCollection<string> _messageQueue = new BlockingCollection<string>(1024); private readonly Thread _writerThread; public BlockingQueue()
{
_messageQueue = new BlockingCollection<string>(1024);
_writerThread = new Thread(Process)
{
IsBackground = true,
};
//Task.Run(()=> { Process(); });
_writerThread.Start();
}
public void Process()
{
while (!_messageQueue.IsCompleted)
{
var x = _messageQueue.Take();
}
} public void AddMessage(string message)
{
_messageQueue.Add(message);
}
}
C# 队列Queue,ConcurrentQueue,BlockingCollection 并发控制lock,Monitor,信号量Semaphore的更多相关文章
- 互斥锁lock、信号量semaphore、事件Event、
1.互斥锁lock 应用在多进程中互斥所lock:互斥锁是进程间的get_ticket互相排斥进程之间,谁先枪占到资源,谁就先上锁,等到解锁之后,下一个进程在继续使用.# 语法: 上锁: lock.a ...
- C#各种同步方法 lock, Monitor,Mutex, Semaphore, Interlocked, ReaderWriterLock,AutoResetEvent, ManualResetEvent
看下组织结构: System.Object System.MarshalByRefObject System.Threading.WaitHandle System.Threading.Mutex S ...
- Python进阶(3)_进程与线程中的lock(线程中互斥锁、递归锁、信号量、Event对象、队列queue)
1.同步锁 (Lock) 当全局资源(counter)被抢占的情况,问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期.这种现象称为“线程不安全”.在开发过 ...
- python笔记9 线程进程 threading多线程模块 GIL锁 multiprocessing多进程模块 同步锁Lock 队列queue IO模型
线程与进程 进程 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成.我们编写的程序用来描述进程要完成哪些功能以及如何完成:数据集则是程序在执行过程中所需要 ...
- C# 多线程(lock,Monitor,Mutex,同步事件和等待句柄)
本篇从 Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler 的类关系图开始,希望通过本篇的介绍能对常见的线程同步方法有一个整体的认识,而 ...
- 多线程中的lock,Monitor.Wait和Monitor.Pulse
我们知道lock实际上一个语法糖糖,C#编译器实际上把他展开为Monitor.Enter和Monitor.Exit,即: lock(lockObj) { //... } ////相当于(.Net4以前 ...
- Python进阶【第二篇】多线程、消息队列queue
1.Python多线程.多进程 目的提高并发 1.一个应用程序,可以有多进程和多线程 2.默认:单进程,单线程 3.单进程,多线程 IO操作,不占用CPU python的多线程:IO操作,多线程提供并 ...
- jquery 的队列queue
使用示列代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www ...
- 重新想象 Windows 8 Store Apps (46) - 多线程之线程同步: Lock, Monitor, Interlocked, Mutex, ReaderWriterLock
[源码下载] 重新想象 Windows 8 Store Apps (46) - 多线程之线程同步: Lock, Monitor, Interlocked, Mutex, ReaderWriterLoc ...
随机推荐
- 图像分割必备知识点 | Unet详解 理论+ 代码
文章转自:微信公众号[机器学习炼丹术].文章转载或者交流联系作者微信:cyx645016617 喜欢的话可以参与文中的讨论.在文章末尾点赞.在看点一下呗. 0 概述 语义分割(Semantic Seg ...
- Java基础教程——注释
Java注释 三种注释: public class 注释 { // (1)单行注释 /* * (2)多行注释 */ /**+回车 * (3)文档注释:可以通过javadoc工具生成HTML文档 * & ...
- Java基础教程——正则表达式
正则表达式·Regular Expression 正则表达式就是一个用于匹配字符串的模板,正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别. 主要用到的对象: java.util.rege ...
- fist-第六天冲刺随笔
这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...
- day2(RESTful风格API)
1.RESTful风格API 详情查看博客地址:https://www.cnblogs.com/xiaonq/p/10053234.html 1.1 什么是RESTful REST与技术无关,代表的 ...
- 第二十章、QTableView与QStandardItemModel开发实战:展示Excel文件内容
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.概述 在前面<第十九章.Model/View开发:QTableView的功能及属性> ...
- 第八章、Designer组件属性编辑界面中QWidget类相关属性详解
老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 声明:本文为老猿Python学习研究精心整理而成,禁止转载. 内容提纲 引言概述QWidget属性列 ...
- Mybatis学习03
title: Mybatis学习03 date: 2020-01-19 13:03:20 tags: Mybatis学习的第二天,内容有日志和分页. <!--more--> 1.日志 1. ...
- 懒松鼠Flink-Boot(Flink+Spring):一款将Flink与Spring生态完美融合的脚手架工程
目录 你可能面临如下苦恼: 接口缓存 重试机制 Bean校验 等等...... 它为流计算开发工程师解决了 有了它你的代码就像这样子: 仓库地址:懒松鼠Flink-Boot 1. 组织结构 2. 技术 ...
- 中间件面试专题:kafka高频面试问题