锁机制
- 用户模式锁
- 易变构造
- 互锁结构:Interlocked
- 旋转锁:SpinLock
- 内核模式锁
- 事件锁
- 信号量:Semaphore
- 互斥锁:mutex
- 读写锁:ReaderWriterLock
- CountDownEvent
- 监视锁
- 混合模式锁
- ManualResetEventSlim
- ReaderWriterLockSlim
- SemaphoreSlim
在.net中锁机制很多,事件锁,信号量,互斥锁,读写锁,互锁,易变构造等。
分类:
<1>用户模式锁
【就是通过一些CPU指令或者一个死循环】来达到thread等待和休眠
<2>内核模式锁
就是通过win32底层的代码,来实现thread的各种操作 Thread.Sleep
<3>混合锁
用户模式+内核模式 xxxslim
用户模式锁
易变构造
一个线程读 一个线程写,在release的某种情况下会有debug。如笔记2中的release版本带来的问题。解决方法:
1.Thread.MemoryBarrier
2.VolatileRead
3.Volatile关键字:不可以底层对代码进行优化,读写都是从Memory中读取。即读取的都是最新的数据
public static volatile bool isStop = false;
static void Main(string[] args)
{
Thread t = new Thread(() =>
{
var isSuccess = false;
while (!isStop)
{
isSuccess = !isSuccess;
}
});
t.Start();
Thread.Sleep(1000);
isStop = true;
t.Join();
Console.WriteLine("主线程执行结束");
Console.ReadKey();
}
互锁结构:Interlocked
【只能做一些简单的计算】
Interlocked.Increment 自增操作 Interlocked.Increment(i);相当于++i;
Interlocked.Decrement 自减操作 Interlocked.Decrement(i);相当于–i;
Interlocked.Add 增加指定的值 Interlocked.Add(ref sum,5);相当于sum+5
Interlocked.ExChange 赋值 Interlocked.ExChange(ref sum,10);相当sum = 10;
Interlocked.CompareExChange 比较赋值
旋转锁:SpinLock
特殊的业务逻辑让Thread在用户模式下进行自旋,欺骗CPU当前Thread正在运行中。用户模式到内核模式到用户模式的循环会消耗内存,通过自旋留在用户模式,防止过度消耗。
static SpinLock sp = new SpinLock();
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
Task.Factory.StartNew(Run);
}
Console.Read();
}
static int nums = 0;
static void Run()
{
for (int i = 0; i < 100; i++)
{
try
{
var b = false;
sp.Enter(ref b);
Console.WriteLine(nums++);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
sp.Exit();
}
}
}
内核模式锁
不到万不得已的情况下,不要使用内核模式,因为代价太大,而且有更多的方式可以替代:用户模式锁、混合锁机制、Lock等。
事件锁
1.自动事件锁【AutoResetEvent】
场景:可以用此锁实现多线程环境下某个变量的自增
true:表示终止状态 false:表示非终止状态
现实中的场景:检票闸机,用车票来实现进站操作。
true(终止状态):闸机里没有车票 =》初始状态
false(非终止状态):闸机里有车票
WaitOne()方法相当于往闸机中放入一张车票
Set()方法相当于从闸机中取出火车票
static AutoResetEvent areLock = new AutoResetEvent(false);
static void Main(string[] args)
{
areLock.WaitOne();
Console.WriteLine("pass");
areLock.Set();
Console.Read();
}
结果:什么也不输出,因为实例化时参数为false,WaitOne方法阻塞了当前线程。
将false改为true,即可输出“pass”
2.手动事件锁【ManualResetEvent】
现实场景:铁道栅栏。如果有火车来,栅栏就会合围阻止行人机动车等通过,等火车走了栅栏就会收起让行人机动车等通过。
true(终止状态):栅栏没有合围,没有阻止行人机动车等通过
false(非终止状态):栅栏合围,阻止行人机动车等通过
注意:
ManualResetEvent和AutoResetEvent模式是不一样的,所以不能混用。 AutoResetEvent收到Set后,一次只能执行一个线程,其它线程继续 WaitOne。就行车站闸机依次检票进展。
ManualResetEvent收到Set后,所有处理WaitOne状态线程均继续执行。就像铁路栅栏打开,所有人车都会蜂拥而过。
以下这两段代码,使用ManualResetEvent会输出两个pass,使用AutoResetEvent只会输出一个pass。
static AutoResetEvent areLock = new AutoResetEvent(true);
static void Main(string[] args)
{
areLock.WaitOne();
Console.WriteLine("pass");
areLock.WaitOne();
Console.WriteLine("pass");
areLock.Set();
Console.Read();
}
static ManualResetEvent mreLock = new ManualResetEvent(true);
static void Main(string[] args)
{
mreLock.WaitOne();
Console.WriteLine("pass");
mreLock.WaitOne();
Console.WriteLine("pass");
Console.Read();
}
信号量:Semaphore
他是通过int数值来控制线程个数
static Semaphore seLock = new Semaphore(1, 1);
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
Task.Factory.StartNew(Run);
}
Console.Read();
}
static int nums = 0;
static void Run()
{
for (int i = 0; i < 100; i++)
{
try
{
seLock.WaitOne();
Console.WriteLine(nums++);
seLock.Release();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
互斥锁:mutex
static Mutex mutex = new Mutex();
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
Task.Factory.StartNew(Run);
}
Console.Read();
}
static int nums = 0;
static void Run()
{
for (int i = 0; i < 100; i++)
{
try
{
mutex.WaitOne();
Console.WriteLine(nums++);
mutex.ReleaseMutex();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
这三种锁都有waione方法,因为他们都是继承于waithandle的。三种锁都是同根生的,底层都是通过SafeWaitHandle来对win32api的引用。
读写锁:ReaderWriterLock
不是从限定个数的角度出发。而是按照读写的角度进行功能分区。
模拟:多个线程读,一个线程写,那么写的线程是否会阻止读取的线程。
static ReaderWriterLock rwLock = new ReaderWriterLock();
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
Task.Factory.StartNew(Read);
}
Task.Factory.StartNew(Write);
Console.Read();
}
static int nums = 0;
/// <summary>
/// 线程读
/// </summary>
static void Read()
{
while (true)
{
Thread.Sleep(10);
rwLock.AcquireReaderLock(int.MaxValue);
Thread.Sleep(10);
Console.WriteLine("当前t = {0} 进行读取 {1}",
Thread.CurrentThread.ManagedThreadId, DateTime.Now);
rwLock.ReleaseReaderLock();
}
}
/// <summary>
/// 线程写
/// </summary>
static void Write()
{
while (true)
{
Thread.Sleep(3000);
rwLock.AcquireWriterLock(int.MaxValue);
Thread.Sleep(5000);
Console.WriteLine("当前t = {0} 进行写入 .................{1}",
Thread.CurrentThread.ManagedThreadId, DateTime.Now);
rwLock.ReleaseWriterLock();
}
}
输出结果:读操作会被写操作打断5秒。说明读写操作是互斥的。
CountDownEvent
限制线程数的一个机制,非常实用。
static CountdownEvent cdeLock = new CountdownEvent(threadcount);
初始化的时候设置了一个默认的thread上限threadcount,当你使用一个thread,这个threadcount就会减一,直到为0,相当于执行结束,task.wait()执行完成,继续执行下一步。
cdeLock.Reset() 重置当前的threadcount。
cdeLock.Signal();当前的threadcount减一操作。
cdeLock.Wait();相当于Task.WaitAll();
static CountdownEvent cdeLock = new CountdownEvent(10);
static void Main(string[] args)
{
cdeLock.Reset(10);
for (int i = 0; i < 10; i++)
{
Task.Factory.StartNew(LoadOrders);
}
cdeLock.Wait();
cdeLock.Reset(5);
for (int i = 0; i < 5; i++)
{
Task.Factory.StartNew(LoadProducts);
}
cdeLock.Wait();
cdeLock.Reset(2);
for (int i = 0; i < 2; i++)
{
Task.Factory.StartNew(LoadUsers);
}
cdeLock.Wait();
Console.WriteLine("执行结束");
Console.Read();
}
static void LoadOrders()
{
Console.WriteLine("LoadOrders...{0}",Thread.CurrentThread.ManagedThreadId);
cdeLock.Signal();
}
static void LoadProducts()
{
Console.WriteLine("LoadProducts...{0}", Thread.CurrentThread.ManagedThreadId);
cdeLock.Signal();
}
static void LoadUsers()
{
Console.WriteLine("LoadUsers...{0}", Thread.CurrentThread.ManagedThreadId);
cdeLock.Signal();
}
}
运行结果:各方法输出是互斥的。
监视锁
限定线程个数的一种锁:Monitor
Enter 锁住一个资源
Exit 解锁某个资源
static object locker = new object();
static void Main(string[] args)
{
for (int i = 0; i < 5; i++)
{
Task.Factory.StartNew(Run);
}
Console.Read();
}
static int nums = 0;
static void Run()
{
for (int i = 0; i < 100; i++)
{
var b = false;
try
{
Monitor.Enter(locker, ref b);
Console.WriteLine(nums++);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
if (b)
Monitor.Exit(locker);
}
}
}
输出结果0-499依次输出。说明5个Task是互斥依次运行的,否则输出是乱序的。
但是,为了严谨,是不是每次都要try catch finally 还要if来判断呢,
为了不这么麻烦,可以使用Lock。
static void Run()
{
for (int i = 0; i < 100; i++)
{
lock (locker)
{
Console.WriteLine(nums++);
}
}
}
因为众多的锁机制中,只有Monitor有专用的语法糖。本质就是利用堆上的同步块实现资源锁定。
总结:Enter中添加的对象,相当于把对象的同步块索引和CLR的同步块数组关联。Exit中释放的资源,相当于把对象的同步块索引和CLR的同步块数组进行了解绑。
注意事项:你锁住的资源一定要让你可访问的线程能够访问到。所以锁住的资源千万不要用值类型。比如上图中的locker变量要放在外面,而不是Run方法里面。
混合模式锁
混合模式锁 = 用户模式锁 + 内核模式锁
混合模式锁有SemaphoreSlim、ManualResetEventSlim、ReaderWriterLockSlim,不用说他们比之前的内核版本性能要高的多。具体使用方法和前面介绍的内核版本一样,只比较其差别。
ManualResetEventSlim
优化点:
<1>构造函数中已经可以不提供默认状态,默认状态就是ManualResetEvent的false状态。
<2>使用Wait来代替WaitOne(WaitOne是WaitHandel祖先类提供的方法),其他方法保持原来的方法名和用法。
<3>支持任务取消
ReaderWriterLockSlim
用EnterReadLock来代替AcquireReaderLock方法,性能要比内核版本要高得多,而且不用输入参数。
SemaphoreSlim
可以在某个时刻想改变默认线程并行个数。
//默认一个线程同时运行,最大10个
static SemaphoreSlim shSlim = new SemaphoreSlim(1, 10);
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Task.Factory.StartNew(Run);
}
//某个时刻想改变默认线程并行个数,从1改成9
Thread.Sleep(2000);
shSlim.Release(9);
Console.Read();
}
static void Run()
{
shSlim.Wait();
Thread.Sleep(1000 * 5);
Console.WriteLine("当前thread = {0}正在运行 {1}",
Thread.CurrentThread.ManagedThreadId,DateTime.Now);
shSlim.Release();
}