多线程和锁和原子操作和内存栅栏(二)

        这里记录下各种锁的使用和使用场景,在多线程场景开发时,我们经常遇到多个线程同时读写一块资源争抢一块资源的情况,比如同时读写同一个字段属性,同时对某个集合进行增删改查,同时对数据库进行读写(这里要用到事物,数据库锁等知识)等,这里往往为了做到数据的一致性和有效性,需要进行对线程之间的在某一时刻或一小段时间的同步操作,来保证数据安全,不会中途造成数据值读写不准确的情况,这个时候一般情况下会用到锁,锁的作用就是为了在某一时刻做线程同步而准备的,但是锁的使用不宜过多,因为本就是多线程场景,而使用多线程就意味着程序是为了做弹性伸缩准备的,线程同步不可避免的会对程序造成性能影响,所以解决多线程之间资源冲突问题最好的方式,就是从一开始就想办法不冲突。

        并发说的是程序在某一时刻被同时请求,而并行指的是程序在运行时的某一时段持续多线程执行任务。

        锁的类型有内核锁,自旋锁,混合锁,

内核锁 是一种基于内核代码的锁,这种锁是一种线程阻塞机制,阻塞的好处是线程会执行到锁阻塞的位置,让线程不继续执行却不消耗CPU计算资源,从而实现线程同步,但不好的一点是,阻塞时,CLR会把C#托管代码转换成非托管代码,然后从非托管代码转换成内核代码,释放时倒序转换回去,这本身就消耗了一定的CPU资源,速度会稍慢,并且在大并发场景下,大量的锁释放导致大量线程同时处于活动状态,使得系统忙于进行线程上下文切换,而不做该做的事情,问题严重

自旋锁 是一种基于内部循环的锁,循环时会不断判断条件,以便于在某一时刻能够退出自旋锁,这种锁的应用场景一般是在并发量比较小,且执行逻辑并不耗时,执行时间比较短的情况下才会使用,自旋锁在锁住期间,由于内部是通过循环来实现线程不继续执行,所以仍然会占用CPU计算资源,这在并发量高的场景下非常不适合使用,大概率导致计算机卡死。

混合锁 是一种内核锁+自旋锁的混合使用,因为单纯使用内核锁或者自旋锁效率都不高,所以尝试把两种技术相结合的方式取长补短,实现锁性能的优化。

Mutex 锁是一种进程同步锁,表示多个进程之间可以共享同一个锁,进程之间通过字符串 获取同一个锁实现进程之间的同步。

通过反编译工具可知,

由于它的内部实现是通过非托管代码,无法查看具体实现,暂时没深入研究。

Semaphore 锁提供了释放锁的次数,构造参数中 initialCount,和maximumCount 指的是初始可释放次数和最大可释放次数,并且提供了进程之间同步的name参数,这和Mutex锁是一样的。

它继承的是WaitHandle ,所以WaitOne 方法是父类的,反编译时可以看到是调用了非托管代码,所以看不到具体实现,

Realease 方法反编译时也是调用了非托管代码,

,所以暂时没深入研究。

SemaphoreSlim 锁是对Semaphore锁的一种简单实现,保留部分Semaphore锁的特点,它是一种混合锁,通过自旋加阻塞的形式,优化纯自旋或阻塞锁的弊端,减少锁的资源消耗,并提供了多种更方便使用的api。

由反编译可知,释放时也是通过阻塞锁的释放方法进行释放

AutoResetEvent 锁是父类EventWaitHandle的一个派生类,只是在实例化时指定了父类构造函数的参数值,

ManualResetEvent 同上,

EventWaitHandle 锁是一个信号锁,而信号量就是锁内部维护的一个bool值或其他可用来表示状态的值。

Set方法的内部实现还是通过非托管代码实现

如之前的方式使用ManualResetEvent 时,锁出现了不一样的结果

ManualResetEvent 锁会在锁是可用时也就是构造传入true时,WaitOne方法不会等待,因为信号量已经是true了,这个锁在信号量设为true时默认会释放所有的阻塞,而AutoResetEvent 锁是单一释放,这在初始化时由父类构造参数决定。它可以改为以下形式使用,在一开始设置信号量为false,让每个线程都阻塞,知道某一刻调用了Set方法,修改信号量,可以同时放开所有线程阻塞,只不过现在无法保证数据的有效性了,因为阻塞放开后,并没有对目标值做原子操作。

ManualResetEventSlim 锁是ManualResetEvent的简易实现,是一种混合锁,内部通过自旋(这里是用for循环)+阻塞的形式,使用方式和ManualResetEvent 一样,

锁一旦放开终止是没用的,因为在后续的执行中并没有等待锁。

Monitor锁 是一个阻塞锁 ,也是一个静态类,他提供最基本的线程阻塞操作,前面有几种锁的阻塞和释放都是通过这个静态类来完成的,它提供两种释放方法Pulse 和 PulseAll 方法来释放单个或者多个锁,且必须用lock关键字或者在Enter 和Exit方法之间进行等待和释放操作。Enter 和Ext 其实就是lock关键字在运行时执行的方法。

释放时并不会按照阻塞时的顺序释放。

CountdownEvent 锁,是一个混合锁,自旋加阻塞的方式,锁内部维护一个ManualResetEventSlim锁变量,是一个代理锁,提供一个int变量,当变量的数值归零时,取消所有线程的等待,利用reset(0),强制释放所有的锁,源码中的实现如下图:

具体使用方式:由于是同时释放,所以结果并不准确。

猜你喜欢

转载自blog.csdn.net/u014690615/article/details/80629084