.Net CLR 中的同步机制(一): 互斥体
随着软硬件技术的发展,无论是在Web服务或者云计算,还是单一的应用程序,串行方式编写的软件越来越少,我们总是可以看见并行的存在。但是并行并不是适合于每一种场景,也完全不是将工作扔到线程池中排队运行那么简单。
由于在进程中,多个线程可能需要访问相同的虚拟内存地址空间,如果不进行控制就很容易出现数据竞争的并发问题,大多是因为操作非原子性和线程时间片的原因引起的,导致的现象会是抛出异常,程序崩溃,数据的值和期望不一致,数据破坏等等,关键有的时候还会随机出现一些问题,这次运行正确,下一次就不正确了,这些问题都不是简单的单元测试就可以测试出来的。为了解决这些问题,windows就提供了同步机制,同步就是唯一能够保证让多线程能正确的使用共享的可变状态的技术。
同步一般分为两种:数据同步和控制同步。
数据同步:一般是指同步的访问某个共享资源主要是内存数据,多个程序以并行的方式使用相同的资源时不会产生干扰。常见的有:lock,Mutex,Monitor,Semaphore等等
控制同步:多线程的运行依赖于程序的控制流,一个线程的运行往往要等待其他线程运行到某个点的通知。如:Event等。
这两种类型我们常常在开发中结合使用。
Windows有很多只能由内核访问的内核对象,如线程对象,文件对象,当然还有我们要介绍的用于同步控制的内核对象:互斥体(Mutex),信号量(Semaphore),事件(Auto-Reset Event, Manual-Reset Event)等。而内核对象是在内核内存中分配的,因此只有在内核态运行的代码中才能访问到它们,访问他们需要使用windows api,从而需要内核切换,所以使用内核对象会比使用其他的原语需要更高的开销。但是内核对象的优势也非常明显,很多都是用户态的同步机制(Win32临界区或CLR的Monitor等)所无法实现的,比如进程间的同步,对于同步的控制,和执行粒度更低的等待,以及托管代码与非托管代码之间的互操作。而可以简单快速可靠使用的api也是我们常常使用的一个关键。
接下来我们先来说一下互斥体。
对于一般性的数据竞争问题,解决方法之一就是使用互斥体来使共享状态的并发访问串行化。互斥体其实就是构建了一段指令临界域,在这个临界域中,同时只能有一个线程来执行临界域中的指令。CLR中的互斥体实现就是Mutex,它的目的就是构建拥有互斥行为的临界域,保证只有一个线程可以进入这个临界域。而在底层基本都是使用原子的比较交换(CAS)来实现,这需要硬件来提供支持。
Mutex继承自System.Threading.WaitHandle。
下面的例子展示了多线程中使用Mutex来实现同步。
class Program{static Mutex m = new Mutex();static void Main(string[] args){Task t1 = new Task(() => CriticalRegion());Task t2 = new Task(() => CriticalRegion());t1.Start();t2.Start();Task.WaitAll(t1, t2);Console.WriteLine("Finished.");Console.ReadLine();}static void CriticalRegion(){m.WaitOne();try{//临界区域Thread.Sleep(5000);//}finally{m.ReleaseMutex();}}}
Mutex的WaitOne方法获取成功以后,互斥体将被线程获取并且标记为未触发。除非拥有互斥体的线城市防它并且重新回到未触发状态,否则其他线程都不能获得这个互斥体。释放的方法为ReleaseMutex。
如果有多个线程在等待同一个Mutex,那么内核将通过FIFO算法来跟踪等待着并决定唤醒哪一个线程。虽然在内核中有一定的顺序,但是我们不能保证我们的多线程程序能够按顺序执行WaitOne方法。所以对我们上层应用来说,首先唤醒哪个线程是未知的。但是你可以通过使用线程的优先级来提升线程首先被执行的可能性。
static void TestPriority()
{
for (int i = 0; i < 10; i++)
{
string taskName = "t" + i;
Thread t1 = new Thread(() => CriticalRegionWithName(taskName));
if (i == 6)
{
t1.Priority = ThreadPriority.Highest;
}
t1.IsBackground = true;
t1.Start();
}
}
static void CriticalRegionWithName(string name)
{
m.WaitOne();
try
{
//临界区域
Thread.Sleep(2000);
Console.WriteLine(name);
//
}
finally
{
m.ReleaseMutex();
}
}
互斥体对象支持递归获取,这意味着当拥有互斥体的线程在互斥体上再次进行等待的时候,这个等待将马上被满足,即使对象处于未触发状态。在互斥体内部,内核维护者一个计数器,对于每个互斥体来说,初始值为0,每次执行获取操作WaitOne的时候,都会增加1,而每次释放Release的时候都会减去1。只有这个互斥体的计数器降为0的时候,其他线程才能够重新获取这个互斥体。所以在使用互斥体Mutex的时候一定要记得调用ReleaseMutex释放。
static void TestRecursion()
{
Task t1 = new Task(() => CriticalRegionWithRecursion("t1"));
Task t2 = new Task(() => CriticalRegionWithRecursion("t2"));
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine("Finished.");
Console.ReadLine();
}
static void CriticalRegionWithRecursion(string taskName)
{
m.WaitOne();
Console.WriteLine(taskName + " got the mutex");
try
{
try
{
m.WaitOne();
Console.WriteLine(taskName + " got the mutex");
//临界区域
Thread.Sleep(3000);
}
finally
{
m.ReleaseMutex();
Console.WriteLine(taskName + " released the mutex");
}
//临界区域
Thread.Sleep(5000);
}
finally
{
m.ReleaseMutex();
Console.WriteLine(taskName + " released the mutex");
}
}
结果:
Mutex还是已有有Owner的锁对象,和Monitor一样。只有获取了带锁的对象才能释放它,如果获取的线程和释放的线程不是同一个线程的话将会产生异常: 从不同步的代码块中调用了对象同步方法。
测试代码:
static void TestOwner()
{
Task.Factory.StartNew(() => { m.WaitOne(); Thread.Sleep(5000); });
Thread.Sleep(2000);
Task.Factory.StartNew(() => { m.ReleaseMutex(); });
}
Mutex可以作为机器范围的,也可以用作进程范围的。在进程范围中,我们可以使用它来同步线程,而在机器范围中,我们可以用Mutex来同步进程。进程同步和线程同步类似,即保证同步的访问在进程间共享的数据。常见的进程间同步是用Mutex来保证进程的在机器上的单实例运行。在内核对象中,同样可以使用与进程间同步的还有Semaphore。
static void Main(string[] args)
{
CheckProcessExists();
Console.ReadLine();
}
static void CheckProcessExists()
{
using (var mutex = new Mutex(false, "Global\\Demo"))
{
if (!mutex.WaitOne(TimeSpan.FromSeconds(3), false))
{
Console.WriteLine("Another app instance is running. Bye!");
return;
}
Console.WriteLine("Runing...");
Console.ReadLine();
}
}
附上MSDN上面的说明 (http://msdn.microsoft.com/zh-cn/library/system.threading.mutex.aspx):
拥有该Mutex互斥体的线程在结束之前没有正确的释放Mutex,比如忘记写finally,忘记ReleaseMutex,或者在递归获取Mutex的过程中计数器出现错误,又或者进程或线程突然中止,那么这个Mutex就会被认为是废弃的互斥体。当互斥体被废弃时,如果不借助操作系统的帮助,那么其他的线程将不能获取到该Mutex,因为此时互斥体并没有被释放。也不用太担心,操作系统会作处理,操作系统可以保证党出现废弃的互斥体时候,有一个等待线程可以被唤醒获取到该互斥体。
Mutex互斥体由于是内核对象,在使用过程中会有大量的内核切换,所以它的效率不是很高。执行的速度约比lock(Monitor)锁慢50倍左右。所以在我们多线程编程中,Mutex的使用频率并不是很高。
测试代码在这里下载
转载于:https://www.cnblogs.com/haoxinyue/archive/2013/01/29/2882152.html
.Net CLR 中的同步机制(一): 互斥体相关推荐
- java 同步锁_java线程中的同步锁和互斥锁有什么区别?
在java中,同步锁和互斥锁英文关键字都是Synchronized,没有本质上的区别,两者都包括对资源的独占,使用起来没有区别.概念上的区别是 1:互斥是通过竞争对资源的独占使用,彼此没有什么关系,执 ...
- Windows® CE 系统中的同步机制
看到篇好文章,呵呵,独乐乐,不如众乐乐 本文转自http://blog.csdn.net/thl789/archive/2006/01/17/582246.aspx ,转载请注明出处 摘要 ... 1 ...
- Linux中的同步机制
1.背景 编写内核代码或驱动代码时需要留意共享资源的保护,防止共享资源被并发访问.所谓并发访问,就是指多个内核路径同时访问和操作相同地址的数据,有可能发生相互覆盖共享数据的情况,造成被访问数据的不一致 ...
- WINCE 系统中的同步机制
摘要 Windows® CE 是微软系列嵌入式平台所采用的操作系统内核.本文讨论了 WinCE 进程/线程之间的同步机制,给出了它们的典型应用场景.这些同步机制包括临界区.互斥体.信号量.事件.互锁函 ...
- Java 中线程同步锁和互斥锁
一 概述 1.1 互斥 所谓互斥,就是不同线程,通过竞争进入临界区(共享的数据和硬件资源),为了防止访问冲突,在有限的时间内只允许其中之一独占性的使用共享资源.如不允许同时写. 1.2 同步 同步关系 ...
- Java中线程同步锁和互斥锁有啥区别?看完你还是一脸懵逼?
首先不要钻概念牛角尖,这样没意义. 也许java语法层面包装成了sycnchronized或者明确的XXXLock,但是底层都是一样的.无非就是哪种写起来方便而已. 锁就是锁而已,避免多个线程对同一个 ...
- 线程同步机制:互斥量、信号量、读写锁、条件变量
一.互斥量(mutex) 互斥量本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁. 对互斥量进行加锁以后,任何其它试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互 ...
- python中的 同步与异步 互斥锁 和 死锁
同步与异步: 同步:指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去. 异步:指进程不需要一直等下去,而是继续执行下面的操 ...
- Linux Kernel中的同步机制的介绍
快速链接: .
最新文章
- windows 系统无法启动windows event log 服务
- 字节跳动凌晨发半个月奖金,网友:我酸了,又是别人家的公司!
- 吐槽express 中间件multer
- Python 数据分析三剑客之 Matplotlib(六):直方图 / 柱状图 / 条形图的绘制
- 将毫秒转换_上海科大:超强电镜技术!原子级分辨率,毫秒级可视化
- python写的软件怎么逆向_python逆向工程:通过代码生成类图
- java 捕获 nullpointerexception,Java 空检查链与捕获NullPointerException
- Google金山词霸体验小记
- 设计模式--创建型模式之抽象工厂模式
- 【Oracle】rollup函数
- win10各个版本激活码到期了
- rpm、lpm是什么意思?
- 初中计算机课程百科,理科、百科
- 机器学习算法 | Python实现k-近邻算法
- 三维地图(3D地图)离线地图开发
- 基于java基于javaweb的管理系统设计与实现怎样选题思路分享
- LabVIEW通讯-TCP
- linux安装搜狗中文,Ubuntu 17.04 安装搜狗中文输入法
- 计算机毕业设计java+ssm车辆租赁网站(源码+系统+mysql数据库+Lw文档)
- docker container的attach和detach模式