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 ...
随机推荐
- open()和with open() as的区别
2020-03-18 20:37:55 open()和with open() as的区别 1 file = open("test.txt","r") 2 fo ...
- P2943 [USACO09MAR]Cleaning Up G
一句话题意:将一个数列分成若干段,每段的不和谐度为该段内不同数字数量的平方,求不和谐度之和的最小值. 令 \(f_i\) 表示前 \(i\) 个数的最小答案,很容易就能写出暴力转移方程:\(f_i=\ ...
- Eclipse导入包
没有包,会报错: 鼠标放上去会有提示: 如果提示消失,可以使用快捷键:Ctrl+1调出提示信息. List的包有两个,集合里的List使用util包.
- 【mq读书笔记】mq消息消费
消息消费以组的的模式开展: 一个消费组内可以包含多个消费者,每一个消费组可订阅多个主题: 消费组之间有集群模式与广播模式两种消费模式:集群模式-主题下的同一条消息只允许被其中一个消费者消费.广播模式- ...
- SpringIOC循环依赖
目录 1. 什么是循环依赖 注意: 这⾥不是函数的循环调⽤,是对象的相互依赖关系. 循环调⽤其实就是⼀个死循环,除⾮有终结 条件. 2. 循环依赖处理机制 2.1 演示场景: 2.2 处理机制简图 总 ...
- rest-framework:认证组件
一 认证简介: 只有认证通过的用户才能访问指定的url地址,比如:查询课程信息,需要登录之后才能查看,没有登录,就不能查看,这时候需要用到认证组件 二 局部使用 models.py class Use ...
- C#:终于有人把 ValueTask、IValueTaskSource、ManualResetValueTaskSourceCore 说清楚了!
目录 1,可用版本与参考资料 2,ValueTask 和 Task 3,编译器如何编译 4,ValueTask 有什么优势 5,ValueTask 创建异步任务 6,IValueTaskSource ...
- uwsgi+nginx的三种配置方式
第一种 vi /etc/uwsgi.ini uwsgi --reload uwsgi.pid vi /etc/nginx/conf.d/iot.conf service nginx restart 第 ...
- Core在IIS的热发布问题或者报错文件已在另一个程序中打开
关于Core发布到IIS的热发布问题,或者覆盖dll文件的时候会报错"文件已在另一个程序中打开",也就是无法覆盖程序的问题,经过百度和分析总结以下几种方案: 一.使用app_off ...
- 第9.11节 Python中IO模块文件打开读写操作实例
为了对前面学习的内容进行一个系统化的应用,老猿写了一个程序来进行文件相关操作功能的测试. 一. 测试程序说明 该程序允许测试人员选择一个文件,自己输入文件打开模式.写入文件的位置以及写入内容,程序按照 ...