1、为多个线程共享的变量提供原子操作。Interlocked
public static class Interlocked
2、互斥锁Mutex 限制只能有一个访问
Mutex m = new Mutex();
m.WaitOne();
Console.WriteLine("1");
m.ReleaseMutex();
3、多个访问线程数量锁SemaphoreSlim
static SemaphoreSlim _semaphore = new SemaphoreSlim(4);//限制4个线程最多同时访问
//下面方法占用一个
_semaphore.Wait();
_semaphore.Release();
4、线程间通知AutoResetEvent
AutoResetEvent myResetEvent = new AutoResetEvent(false);
如过设置true,那么myResetEvent.WaitOne()就能够在程序一启动就获得运行权;就相当于一启动程序就自动运行了一次myResetEven.Set();
否则myResetEven.Set()成功运行后,myResetEven.WaitOne()才能够获得运行机会;
false:无信号,子线程的WaitOne方法不会被自动调用
true:有信号,子线程的WaitOne方法会被自动调用myResetEvent.WaitOne();
当调用myResetEvent.Set();后,waitone后的代码才执行。WaitOne 或是WaitAll 最好都加上超时时间。
AutoResetEvent在.Net多线程编程中,经常用到。当某个线程调用WaitOne方法后,信号处于发送状态,该线程会得到信号, 程序就会继续向下执行,否则就等待。而且 AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为不发送状态,其他调用WaitOne的线程只有继续等待.也就是说,AutoResetEvent一次只唤醒一个线程,其他线程还是堵塞。(!!!只能通知到单个线程)
Reset ():将事件状态设置为非终止状态,导致线程阻止;如果该操作成功,则返回true;否则,返回false。
Set ():将事件状态设置为终止状态,允许一个或多个等待线程继续;如果该操作成功,则返回true;否则,返回false。
WaitOne(): 阻止当前线程,直到收到信号。
WaitOne(TimeSpan, Boolean) :阻止当前线程,直到当前实例收到信号,使用 TimeSpan 度量时间间隔并指定是否在等待之前退出同步域。
WaitAll():等待全部信号。
注意:AutoResetEvent采用的是内核时间,所以等待时间不能太长。使用后面的ManualResetEventSlim类更好
5、多线程间通知ManualResetEvent
与AutoResetEvent类似,但是ManualResetEvent是通知所有准备好的线程。ManualResetEvnetSlim的整个工作方式有点像人群通过大门。
AutoResetEvent事件像一个旋转门,一次只允许一人通过。ManualResetEventSlim是Manual-ResetEvent的混合版本,一直保持大门敞开直到手动调用Reset方法。当调用_mainEvent.Set时,相当于打开了大门从而允许准备好的线程接收信号并继续工作。然而如果线程还处于睡眠状态,没有赶上时间。当调用_mainEvent.Reset相当于关闭了大门。最后一个线程已经准备好执行,但是不得不等待下一个信号,即要等待好几秒钟。
6、CountDownEvent等待一定数量操作的类
// <summary>
/// 创建 CountdownEvent 实例,指定达到次数为 2 次
/// </summary>
public static CountdownEvent _countdown = new CountdownEvent(2);
/// <summary>
///
/// </summary>
/// <param name="message">消息提示</param>
/// <param name="seconds">时间(秒)</param>
public static void PerformOperation(string message, int seconds)
{
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine(message);
//添加一次执行数量
_countdown.Signal();
}
/// <summary>
/// 输出
/// </summary>
public static void Print()
{
// Starting two operations
var t1 = new System.Threading.Thread(() => PerformOperation("Operation 1 is completed", 4));
var t2 = new System.Threading.Thread(() => PerformOperation("Operation 2 is completed", 8));
t1.Start();
t2.Start();
// 如果 调用 _countdown.Signal() 没达到指定的次数
// 那么 _countdown.Signal() 将一直阻止,继续等待
_countdown.Wait(); // 阻止当前线程
// Both operation have been completed
_countdown.Dispose(); // 释放资源
}
7、Barrier 线程指定次数调用SignalAndWait后,自动调用回调函数Barrier
static void Main(string[] args)
{
var t1 = new Thread(() => PlayMusic("the guitarist", "play an amazing solo", 5));
var t2 = new Thread(() => PlayMusic("the singer", "sing the song", 2));
t1.Start();
t2.Start();
Console.ReadKey();
}
//指定Barrier在线程调用两次SignalAndWait后执行回调函数。
static Barrier _barrier = new Barrier(2, b => Console.WriteLine("End of phase {0}", b.CurrentPhaseNumber + 1));
static void PlayMusic(string name,string message,int seconds)
{
for (int i = 1; i < 3; i++)
{
Console.WriteLine("---------------------------------------------");
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine("{0} starts to {1}", name, message);
Thread.Sleep(TimeSpan.FromSeconds(seconds));
Console.WriteLine("{0} finishes to {1}", name, message);
_barrier.SignalAndWait();
}
}
8、ReaderWriterLockSlim 线程对集合读写的安全机制,允许多线程同时读取,以及独占用
ReaderWriterLockSlim 类支持三种锁定模式:Read,Write,UpgradeableRead。这三种模式对应的方法分别是 EnterReadLock,EnterWriteLock,EnterUpgradeableReadLock 。再就是与此对应的 TryEnterReadLock,TryEnterWriteLock,TryEnterUpgradeableReadLock,ExitReadLock,ExitWriteLock,ExitUpgradeableReadLock。
Read 和 Writer 锁定模式比较简单易懂:Read 模式是典型的共享锁定模式,任意数量的线程都可以在该模式下同时获得锁;Writer 模式则是互斥模式,在该模式下只允许一个线程进入该锁。UpgradeableRead 锁定模式可能对于大多数人来说比较新鲜,但是在数据库领域却众所周知。
备注及注意事项
1、对于同一把锁、多个线程可同时进入读模式。
2、对于同一把锁、同时只允许一个线程进入写模式。
3、对于同一把锁、同时只允许一个线程进入可升级的读模式。
4、通过默认构造函数创建的读写锁是不支持递归的,若想支持递归 可通过构造 ReaderWriterLockSlim(LockRecursionPolicy) 创建实例。
5、对于同一把锁、同一线程不可两次进入同一锁状态(开启递归后可以)
6、对于同一把锁、即便开启了递归、也不可以在进入读模式后再次进入写模式或者可升级的读模式(在这之前必须退出读模式)。
7、再次强调、不建议启用递归。
8、读写锁具有线程关联性,即两个线程间拥有的锁的状态相互独立不受影响、并且不能相互修改其锁的状态。
9、升级状态:在进入可升级的读模式 EnterUpgradeableReadLock后,可在恰当时间点通过EnterWriteLock进入写模式。
10、降级状态:可升级的读模式可以降级为读模式:即在进入可升级的读模式EnterUpgradeableReadLock后, 通过首先调用读取模式EnterReadLock方法,然后再调用 ExitUpgradeableReadLock 方法。
简单的说,当某个线程进入读取模式时,此时其他线程依然能进入读取模式,假设此时一个线程要进入写入模式,那么他不得不被阻塞。直到读取模式退出为止。
同样的,如果某个线程进入了写入模式,那么其他线程无论是要写入还是读取,都是会被阻塞的。
进入写入/读取模式有2种方法:
EnterReadLock尝试进入写入模式锁定状态。
TryEnterReadLock(Int32) 尝试进入读取模式锁定状态,可以选择整数超时时间。
EnterWriteLock 尝试进入写入模式锁定状态。
TryEnterWriteLock(Int32) 尝试进入写入模式锁定状态,可以选择超时时间。
退出写入/读取模式有2种方法:
ExitReadLock 减少读取模式的递归计数,并在生成的计数为 0(零)时退出读取模式。
ExitWriteLock 减少写入模式的递归计数,并在生成的计数为 0(零)时退出写入模式。
为了降低阻塞,可以使用EnterUpgradeableReadLock和ExitUpgradeableReadLock方法,先获取读锁后读取数据,如发现必须修改,只需要使用EnterWriteLock升级锁,然后快速的进行一次写操作,最后使用ExitWriteLock释放写锁。
ReaderWriterLockSlim类提供了可升级读模式,这种方式和读模式的区别在于它还有通过调用 EnterWriteLock 或 TryEnterWriteLock 方法升级为写入模式。 因为每次只能有一个线程处于可升级模式。进入可升级模式的线程,不会影响读取模式的线程,即当一个线程进入可升级模式,任意数量线程可以同时进入读取模式,不会阻塞。如果有多个线程已经在等待获取写入锁,那么运行EnterUpgradeableReadLock将会阻塞,直到那些线程超时或者退出写入锁。
9、SpinWait 混合同步线程等待
它是一个混合同步构造,被设计为使用用户模式等待一段时间,然后切换到内核模式以节省CPU时间。
//初始化
var w = new SpinWait();
//添加一次迭代
w.SpinOnce();
我们使用了SpinWait版本,刚开始,SpinWait尝试使用用户模式,在9个迭//代后,开始切换线程为阻塞状态。如果尝试测量该版本的CPU负载,在Windows任务管理器将不会看到任何CPU的使用。