上一篇介绍了同步事件EventWaitHandle,以及它的两个子类型AutoResetEvent和ManualResetEvent。下面接着介绍WaitHandle的另外两个子类型Mutex和Semaphore。

互斥体Mutex

互斥体Mutex也是Windows用来进行线程同步的内核对象。当两个或更多线程需要同时访问一个共享资源时,可以使用 Mutex 同步基元,它只向一个线程授予对共享资源的独占访问权。 如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。

说到这里,可以回想一下前面介绍的lock和Monitor,其实如果只是进行同一进程之间的线程同步,建议更多的使用lock和Monitor,因为Mutex示一个内核对象,所以使用的时候会产生很大的性能消耗;但是,Mutex可以支持不同进程之间的线程同步,同时允许同一个线程多次重复访问共享区,但是对于别的线程那就必须等待。因此,要根据应用的需求来选择lock/Monitor还是Mutex。

对于Mutex,通过父类WaitHandle的WaitOne方法来请求互斥体的所属权。

下面看看Mutex中的常用方法:

  • 实例方法:
    • Mutex(bool initiallyOwned):创建Mutex实例,并设置互斥体的初始状态(终止/非终止)
    • Mutex(bool initiallyOwned, string name):通过name参数来创建一个命名的互斥体(也叫全局互斥体),通过这个名字可以进行不同进程之间的线程同步。反之,没有名称的互斥体被称为局部互斥体
    • Mutex(bool initiallyOwned, string name, out bool createdNew):如果两个进程会创建同名的Mutex,那么后执行的线程返回的Mutex实例只是指向了同名的Mutex而已。那么,这里我们可以通过createdNew参数来判断该进程中的Mutex实例是否是新创建的
    • ReleaseMutex():线程通过ReleasMutex方法释放互斥体的所属权
  • 静态方法:
    • OpenExisting(string name):打开指定名称为 mutex(如果已经存在)

互斥体Mutex也有终止和非终止状态,调用ReleaMutex 后互斥体的状态设定为终止,直到其他线程占有互斥体,但是如果没有线程拥有互斥体的话,该互斥体的状态将一直保持终止状态。

注意:WaitOne方法和ReleaseMutex方法的使用次数必须一致。前面介绍过互斥体Mutex允许同一个线程多次重复访问共享区,也就是说,拥有互斥体的线程可以在对 WaitOne 的重复调用中请求相同的互斥体而不会阻止其执行, 但线程必须调用 ReleaseMutex 方法同样多的次数以释放互斥体的所属权。

下面可以个互斥体Mutex的例子:

namespace MutexTest
{
class Program
{
private static Mutex mutex; static void Main(string[] args)
{
string mutexName = "MutexTest";
try
{
//尝试打开已有的互斥体
mutex = Mutex.OpenExisting(mutexName);
}
catch (WaitHandleCannotBeOpenedException e)
{
Console.WriteLine("Mutex named {0} is not exist, error message: {1}", mutexName, e.Message);
//Mutex实例初始为非终止状态
mutex = new Mutex(false, mutexName);
Console.WriteLine("Create Mutex {0}", mutexName);
} for (int i = ; i < ; i++)
{
//通过线程池调用时间打印函数
ThreadPool.QueueUserWorkItem(new WaitCallback(GetCurrentTime));
} Console.ReadLine();
}
static void GetCurrentTime(object state = null)
{
//调用WaitOne来等待信号
mutex.WaitOne();
try
{
//Mutex支持同一个线程多次重复访问共享区,所以加上下面的WaitOne一样可以工作
//mutex.WaitOne();
Thread.Sleep();
Console.WriteLine("Current Time is: {0}", DateTime.Now);
//mutex.ReleaseMutex();
}
finally
{
mutex.ReleaseMutex();
}
}
}
}

代码的输出为下:

当我们连续两次运行MutexTest.exe时,可能得到以下的输出。通过输出可以看到,后面启动的进程可以获得同样的互斥体,然后进行进程之间的线程同步。

通过Mutex运行单一程序

由于命名的Mutex可以跨越进程,所以Mutex还有一个常用的场景就是用来限制一次只能运行一个程序实例。直接看一个简单的例子:

namespace SingleAppWithMutex
{
class Program
{
static void Main(string[] args)
{
bool canCreateNew; //通过canCreateNew来判断是否是新建的互斥锁实例
Mutex m = new Mutex(true, "SingleApp", out canCreateNew);
if (canCreateNew)
{
Console.WriteLine("Start App to print time");
new Thread(() => {
while (true)
{
Thread.Sleep();
Console.WriteLine("Current time is {0}", DateTime.Now);
}
}).Start();
}
else
{
Console.WriteLine("App already start");
} Console.Read();
}
}
}

在程序运行过程中,如果尝试再次启动程序,就会得到"App already start "输出。这里主要利用了Mutex(bool initiallyOwned, string name, out bool createdNew)构造函数中的createdNew这个参数。

信号量Semaphore

前面所介绍的线程同步机制中,都是同时只能有一个线程进入共享区(ManualResetEvent除外),这里我们看一下通过信号量进行线程同步,这种线程同步方式也可以支持多个线程同时进入共享区。

信号量通过一种计数的方式来控制同时进入共享区的线程的数量,信号量的计数在每次线程进入信号量时减小,在线程释放信号量时增加。 当计数为零时,后面的请求将被阻塞,直到有其他线程释放信号量。 当所有的线程都已释放信号量时,计数达到创建信号量时所指定的最大值。

对于信号量,也是通过调用WaitOne 方法(从 WaitHandle 类继承)进入信号量。

下面看看Semaphore中的常用方法:

  • 实例方法:
    • Semaphore(int initialCount, int maximumCount):创建信号量实例,并设置初始计数值和最大计数值
    • Semaphore(int initialCount, int maximumCount, string name):创建命名的信号量,可以支持进程之间的线程同步
    • Release():退出信号量,并且计数加一
    • Release(int releaseCount):以指定的次数退出信号量,并更新计数

信号量分为两种类型:局部信号量和已命名的系统信号量。已命名的系统信号量在整个操作系统中都可见,可用于同步进程活动。 您可以创建多个 Semaphore 对象来表示同一个已命名的系统信号量,也可以使用 OpenExisting 方法打开现有的已命名系统信号量。

注意:Semaphore 类不对 WaitOne 或 Release 调用强制线程标识。 程序员负责确保线程释放信号量的次数不能太多。 例如,假定信号量的最大计数为二,线程 A 和线程 B 都进入信号量。 如果线程 B 中的编程错误导致它两次调用 Release,则两次调用都成功。 这样,信号量的计数就已经达到了最大值,所以,当线程 A 最终调用 Release 时,将引发 SemaphoreFullException

下面看一个信号量的例子:

namespace SemaphoreTest
{
class Program
{
//创建信号量实例,
private static Semaphore sem; static void Main(string[] args)
{
bool createdNew;
sem = new Semaphore(, , "SemaphoreTest", out createdNew);
if (createdNew)
{
Thread.Sleep();
//退出信号量三次,并且计数加三
sem.Release();
} for (int i = ; i < ; i++)
{
Thread t = new Thread(new ParameterizedThreadStart(Worker));
t.Start(i);
} Console.Read();
} private static void Worker(object index)
{
//调用WaitOne来等待信号
sem.WaitOne();
Console.WriteLine("---> Thread {0} enter Critical code section", index.ToString());
Random ran = new Random();
Thread.Sleep(ran.Next(, ));
Console.WriteLine(" Thread {0} exit Critical code section <---", index.ToString());
//退出信号量,并且计数加一
sem.Release(); }
}
}

代码的输出为,由于代码中信号量实例的最大计数为三,所以可以同时访问共享去的最大线程数就是三。

当我们连续执行SemaphoreTest.exe两次时,可以看到两个进程之间的线程同步,依然同时只能有最多三个线程进入共享区。

总结

本文介绍了如何通过互斥体Mutex和信号量Semaphore进行线程同步。

到这里,同步句柄WaitHandle 及其子类EventWaitHandle,AutoResetEvent,ManualResetEvent,Mutex和Semaphore就都介绍完了。通过内核对象进行线程同步会带来很大的性能开销,但是,由于内核对象属于操作系统,对所有进程可见,所以利用这些线程同步方式可以很容易的实现不同进程之间的线程同步。

线程同步 –Mutex和Semaphore的更多相关文章

  1. 从零开始构建一个Reactor模式的网络库(一) 线程同步Mutex和Condition

    最近在学习陈硕大神的muduo库,感觉写的很专业,以及有一些比较“高级”的技巧和设计方式,自己写会比较困难. 于是打算自己写一个简化版本的Reactor模式网络库,就取名叫mini吧,同样只基于Lin ...

  2. JDK5.0 特性-线程同步装置之Semaphore

    来自:http://www.cnblogs.com/taven/archive/2011/12/17/2291474.html import java.util.ArrayList; import j ...

  3. [b0032] python 归纳 (十七)_线程同步_信号量Semaphore

    代码: # -*- coding: utf-8 -*- """ 多线程并发同步 ,使用信号量threading.Semaphore 逻辑: 多个线程,对同一个共享变量 , ...

  4. C#线程同步--限量使用

    问题抽象:当某一资源同一时刻允许一定数量的线程使用的时候,需要有个机制来阻塞多余的线程,直到资源再次变得可用.线程同步方案:Semaphore.SemaphoreSlim.CountdownEvent ...

  5. UDP、线程、mutex锁(day15)

    一.基于UDP的网络编程模型 服务器端 .创建socket. .将fd和服务器的ip地址和端口号绑定 .recvfrom阻塞等待接收客户端数据 .业务处理 .响应客户端 客户端: .创建socket ...

  6. 第9章 用内核对象进行线程同步(3)_信号量(semaphore)、互斥对象(mutex)

    9.5 信号量内核对象(Semaphore) (1)信号量的组成 ①计数器:该内核对象被使用的次数 ②最大资源数量:标识信号量可以控制的最大资源数量(带符号的32位) ③当前资源数量:标识当前可用资源 ...

  7. 经典线程同步 信号量Semaphore

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...

  8. 经典线程同步 互斥量Mutex

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...

  9. 秒杀多线程第八篇 经典线程同步 信号量Semaphore

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <且不超过最大资源数量. 第三个參数能够用来传出先前的资源计数,设为NULL表示不须要传出. 注意:当 ...

随机推荐

  1. Java设计模式(16)中介模式(Mediator模式)

    Mediator定义:用一个中介对象来封装一系列关于对象交互行为. 为何使用Mediator模式/中介模式 各个对象之间的交互操作非常多,每个对象的行为操作都依赖彼此对方,修改一个对象的行为,同时会涉 ...

  2. Java并发框架??AQS中断的支持

    线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠 ...

  3. [Intellij] Intellij IDEA 使用中遇见的问题

    问题集锦 [IntelliJ IDEA14 + tomcat 设置热部署] 点击deployment查看Deploy at the server startup 中tomcat每次所运行的包是 xxx ...

  4. Elasticsearch与Solr 选型

    转自:http://blog.csdn.net/jameshadoop/article/details/44905643 搜索引擎选择: Elasticsearch与Solr 搜索引擎选型调研文档 E ...

  5. SAP财务供应链与金库管理的联系与区别

    SAP Treasure Module & Cash Fund , Risk Management   本文简要阐述一下什么是财务供应链管理(FSCM),什么是金库管理(Treasury Ma ...

  6. [转]android ANR产生原因和解决办法

    ANR (Application Not Responding) ANR定义:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(AN ...

  7. WebService系列二:使用JDK和CXF框架开发WebService

    一.使用JDK开发WebService 服务端程序创建: 1.新建一个JDK开发webservice的服务端maven项目JDKWebServiceServer 2. 定义一个接口,使用@WebSer ...

  8. 利用smba实现windows上写程序,linux上运行

    1.在linux下载程序代码(确保获取正确的文件属性) 2.在windows编写代码,对于已有代码,不改变文件权限,如原先为755的,更改文件内容后依然是755的文件,如果要新建文件,默认为644,其 ...

  9. CI框架 -- 核心文件 之 Benchmark.php

    Benchmark.php文件中定义的CI_Benchmark类可以让你标记点,并计算它们之间的时间差.还可以显示内存消耗. Benchmarking类库,它是被系统自动被加载的,不需要手工加载 cl ...

  10. Linux SD卡驱动开发(四) —— SD 控制器之真正的硬件操作

    前面对SD卡控制器有了一个主要的介绍.事实上SD控制器层更过的意义是为core层提供一种操作SD卡硬件的一种方法.当然不同的控制器对硬件控制的方法不尽同样,可是他们终于都能像core层提交一个统一的封 ...