多个线程同时使用共享对象会造成很多问题。同步这些线程使得对共享对象的操作能够以正确的顺序执行是非常重要的。
竞争条件问题: 多线程的执行并没有正确同步
其他线程需要依次等待,称为线程同步
(1) 如果无需共享对象,那么久无须进行线程同步。请尽可能避免在多个线程间使用单一对象。
(2 ) 使用原子操作:意味着,一个操作只占用一个量子的时间,一次就可以完成。所有只有当前操作完成后,
其他线程才能执行其他操作。因此,无需等待其他线程等待当前操作完成。
(3) 将等待的线程设置为阻塞状态。
当线程处于阻塞状态时,只会占用尽可能少的CPU时间,。
这意味着将引入至少一次上下文切换。
上下文切换:context switch 操作系统的线程调度器。会保存等待的线程的状态,并切换到另一个线程,依次恢复等待的线程状态。需要消耗大量的资源。如果线程要被挂起很长时间,这就是值得的。
这种方式又被成为内核模式,因为只有操作系统内核才能阻止线程使用CPU时间。
如果只是等待一小段时间,最好只是简单的等待,而不是将线程切换到阻塞状态。
虽然线程等待时会消耗CPU时间,但我们节省了上下文切换耗费的CPU时间。这种方式成为 用户模式。轻量,速度快。
但如果需要等待足够长时间,则会浪费大量的CPU时间。
混合模式: 混合模式会先尝试使用用户模式等待,如果县城等待了足够长的时间,则会切换到阻塞状态以节省CPU资源
执行基本的原子操作:
原子操作:一个执行只占用一个CPU原子时间,从而不用阻塞线程就可以避免竞争条件
InterLocked类: 为多个线程 共享的变量提供原子操作。
InterLocked.Increment(int a)://以原子操作的形式递增指定变量的值并存储结果。
InterLocked.Decrement(int b);//以院子操作的形式递减指定变量的值并存储结果。
借助InterLocked类,我们无需锁定任何对象即可获得正确的结果,在一个原子CPU时间内已经完成了对数据的操作。
MUTEX 互斥锁:
只对一个线程授予对共享资源的访问权限。
Mutex(Boolen,String);使用Boolean值(指示 调用线程是否具有互斥体的初始所有权以及字符串是否为互斥体的名称)
初始化Mutex类的新实例
mutex.WaitOne(); 阻止当前线程,直到当前实例收到信号为止,同时使用TimeSpan指定时间间隔,并指定是否在等待之前退出同步域。
boolean: 如果等待之前推出上下文同步域(如果在同步上下文中),并在稍后重新获取他,则为true,否则为false
如果当前实例收到信号,则为 true;否则为 false。
using (var m = new Mutex(false, MutexName))
{
if (!m.WaitOne(TimeSpan.FromSeconds(2), false))
{
Console.WriteLine("second instance is running!");
Console.ReadLine();
}
else
{
Console.WriteLine("Running!");
Console.ReadLine();
m.ReleaseMutex();
}
}
当主程序启动时,定义了一个制定名称的互斥量,设置initialOwner标志位false
表示,如果互斥量已经被创建,则允许互斥量获取该程序。如果没有获取到互斥量,程序则简单地显示running
,等待直到按下任何键。第二个程序开始执行,等待5秒,没能获取到信号量后,则运行里面代码。
具名的互斥量 是全局的操作系统对象! 请务必正确关闭互斥量。
该方式可用于不同的程序中的同步线程。
semaphore 信号量;
semaphoreslim:信号量轻量级版本。该类限制了同时访问一个资源的线程数量。
semaphore.Wait()// : 阻止当前线程,知道它可以进入semaphoreSlim为止
Thread.Sleep(TimeSpan.FromSecond(seconds);//在上面完成等待,获得信号量,然后开始做事
semaphore.Release();//释放信号量
混合模式,在等待时间很短的情况下无需切换上下文。
Semaphore 类中使用纯粹的内核模式。SemaphoreSlim 不适用windows内核信号量。而且不支持进程间同步。
所以跨进程同步的场景下可以使用Semaphore。
AutoResetEvent类:
AutoResetEvent类来从一个线程向另一个线程发送通知。
AutonResetEvent 类 可以通知等待的线程有某件事发生。
AutonResetEvent(false); 新实例初始化AutoResetEvent 使用boolean值,该值只是是否将出事状态设置为终止状态类
autoResetEvent.WaitOne();//Blocks the current thread until the current WaitHandle receives a single.
autoResetEvent.Reset();// 将事件状态设置为非终止,从而导致线程受阻
AutoRestEvent 类采用的是内核时间模式,所以等待的时间不能太长。
ManualResetEventSlim
MaualResetEventSlim类来在线程间以更灵活的方式传递信号。
manualResetEventSlim.Wait();// 等待打开大门
manualReseteventSlim.Set();//打开大门
manualResetEventSlim.Reset();//关闭大门
CountDownEvent
CountDownEvent类来等待直到一定数量的操作完成
static CountDownEvent _countdown = new CountdownEvent(2);
_countDown.Wait();需要等收到所有线程的_countDown.Single();之后,才会结束。