信号量semaphore

信号量内核对象                                      

信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。


图3 使用信号量对象控制资源

  下面结合图例3来演示信号量对象对资源的控制。在图3中,以箭头和白色箭头表示共享资源所允许的最大资源计数和当前可用资源计数。初始如图(a)所示,最大资源计数和当前可用资源计数均为4,此后每增加一个对资源进行访问的线程(用黑色箭头表示)当前资源计数就会相应减1,图(b)即表示的在3个线程对共享资源进行访问时的状态。当进入线程数达到4个时,将如图(c)所示,此时已达到最大资源计数,而当前可用资源计数也已减到0,其他线程无法对共享资源进行访问。在当前占有资源的线程处理完毕而退出后,将会释放出空间,图(d)已有两个线程退出对资源的占有,当前可用计数为2,可以再允许2个线程进入到对资源的处理。可以看出,信号量是通过计数来对线程访问资源进行控制的,而实际上信号量确实也被称作Dijkstra计数器。

  使用信号量内核对象进行线程同步主要会用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函数。其中,CreateSemaphore()用来创建一个信号量内核对象,其函数原型为:

HANDLE CreateSemaphore(
 LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针
 LONG lInitialCount, // 初始计数
 LONG lMaximumCount, // 最大计数
 LPCTSTR lpName // 对象名指针
);

  参数lMaximumCount是一个有符号32位值,定义了允许的最大资源计数,最大取值不能超过4294967295。lpName参数可以为创建的信号量定义一个名字,由于其创建的是一个内核对象,因此在其他进程中可以通过该名字而得到此信号量。OpenSemaphore()函数即可用来根据信号量名打开在其他进程中创建的信号量,函数原型如下:

HANDLE OpenSemaphore(
 DWORD dwDesiredAccess, // 访问标志
 BOOL bInheritHandle, // 继承标志
 LPCTSTR lpName // 信号量名
);

  在线程离开对共享资源的处理时,必须通过ReleaseSemaphore()来增加当前可用资源计数。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值,而其他线程却因为当前可用资源计数为0而仍无法进入的情况。ReleaseSemaphore()的函数原型为:

BOOL ReleaseSemaphore(
 HANDLE hSemaphore, // 信号量句柄
 LONG lReleaseCount, // 计数递增数量
 LPLONG lpPreviousCount // 先前计数
);

  该函数将lReleaseCount中的值添加给信号量的当前资源计数,一般将lReleaseCount设置为1,如果需要也可以设置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在试图进入共享资源的线程函数入口处,主要用来判断信号量的当前可用资源计数是否允许本线程的进入。只有在当前可用资源计数值大于0时,被监视的信号量内核对象才会得到通知。

  信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为没一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。下面给出的示例代码即展示了类似的处理过程:

// 信号量对象句柄
HANDLE hSemaphore;
UINT ThreadProc15(LPVOID pParam)
{
 // 试图进入信号量关口
 WaitForSingleObject(hSemaphore, INFINITE);
 // 线程任务处理
 AfxMessageBox("线程一正在执行!");
 // 释放信号量计数
 ReleaseSemaphore(hSemaphore, 1, NULL);
 return 0;
}
UINT ThreadProc16(LPVOID pParam)
{
 // 试图进入信号量关口
 WaitForSingleObject(hSemaphore, INFINITE);
 // 线程任务处理
 AfxMessageBox("线程二正在执行!");
 // 释放信号量计数
 ReleaseSemaphore(hSemaphore, 1, NULL);
 return 0;
}
UINT ThreadProc17(LPVOID pParam)
{
 // 试图进入信号量关口
 WaitForSingleObject(hSemaphore, INFINITE);
 // 线程任务处理
 AfxMessageBox("线程三正在执行!");
 // 释放信号量计数
 ReleaseSemaphore(hSemaphore, 1, NULL);
 return 0;
}
……
void CSample08View::OnSemaphore()
{
 // 创建信号量对象
 hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
 // 开启线程
 AfxBeginThread(ThreadProc15, NULL);
 AfxBeginThread(ThreadProc16, NULL);
 AfxBeginThread(ThreadProc17, NULL);
}


图4 开始进入的两个线程


图5 线程二退出后线程三才得以进入

  上述代码在开启线程前首先创建了一个初始计数和最大资源计数均为2的信号量对象hSemaphore。即在同一时刻只允许2个线程进入由hSemaphore保护的共享资源。随后开启的三个线程均试图访问此共享资源,在前两个线程试图访问共享资源时,由于hSemaphore的当前可用资源计数分别为2和1,此时的hSemaphore是可以得到通知的,也就是说位于线程入口处的WaitForSingleObject()将立即返回,而在前两个线程进入到保护区域后,hSemaphore的当前资源计数减少到0,hSemaphore将不再得到通知,WaitForSingleObject()将线程挂起。直到此前进入到保护区的线程退出后才能得以进入。图4和图5为上述代脉的运行结果。从实验结果可以看出,信号量始终保持了同一时刻不超过2个线程的进入。

  在MFC中,通过CSemaphore类对信号量作了表述。该类只具有一个构造函数,可以构造一个信号量对象,并对初始资源计数、最大资源计数、对象名和安全属性等进行初始化,其原型如下:

CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );

  在构造了CSemaphore类对象后,任何一个访问受保护共享资源的线程都必须通过CSemaphore从父类CSyncObject类继承得到的Lock()和UnLock()成员函数来访问或释放CSemaphore对象。与前面介绍的几种通过MFC类保持线程同步的方法类似,通过CSemaphore类也可以将前面的线程同步代码进行改写,这两种使用信号量的线程同步方法无论是在实现原理上还是从实现结果上都是完全一致的。下面给出经MFC改写后的信号量线程同步代码:

// MFC信号量类对象
CSemaphore g_clsSemaphore(2, 2);
UINT ThreadProc24(LPVOID pParam)
{
 // 试图进入信号量关口
 g_clsSemaphore.Lock();
 // 线程任务处理
 AfxMessageBox("线程一正在执行!");
 // 释放信号量计数
 g_clsSemaphore.Unlock();
 return 0;
}
UINT ThreadProc25(LPVOID pParam)
{
 // 试图进入信号量关口
 g_clsSemaphore.Lock();
 // 线程任务处理
 AfxMessageBox("线程二正在执行!");
 // 释放信号量计数
 g_clsSemaphore.Unlock();
 return 0;
}
UINT ThreadProc26(LPVOID pParam)
{
 // 试图进入信号量关口
 g_clsSemaphore.Lock();
 // 线程任务处理
 AfxMessageBox("线程三正在执行!");
 // 释放信号量计数
 g_clsSemaphore.Unlock();
 return 0;
}
……
void CSample08View::OnSemaphoreMfc()
{
 // 开启线程
 AfxBeginThread(ThreadProc24, NULL);
 AfxBeginThread(ThreadProc25, NULL);
 AfxBeginThread(ThreadProc26, NULL);
}
 
<think>首先,用户的问题是:“我想了解互斥锁mutex和信号量semaphore之间的区别请问mutex与semaphore的区别及使用场景”。所以,我需要专注于解释Mutex和Semaphore的区别及使用场景。系统级指令要求:-所有行内数学表达式使用$...$格式。-独立公式使用$$...$$格式并单独成段。-LaTeX语法正确。-使用中文回答。-生成相关问题。-在回答中引用的段落末尾自然地添加引用标识,例如[^1]。-回答结构清晰,帮助用户逐步解决问题。-尽量保证回答真实可靠。参考站内引用:-[^1]:Mutex和Semaphore的目的不同,但Mutex有时被误称为binarysemaphore。-[^2]:在C#多线程中,Mutex和Semaphore是同步工具,各有特点和适用场景。-[^3]:Mutex的函数在内核中定义,包括初始化、锁定等操作。-[^4]:互斥锁与信号量的定义、区别、使用,包括pthread示例。-[^5]:命名Mutex支持跨进程同步。我的回答需要基于这些引用,但不要直接复制;要自然地融入。用户的问题是区别和使用场景,所以我需要结构化地回答。回答结构:1.**介绍**:简要定义Mutex和Semaphore。2.**区别**:详细解释主要区别。-所有权:Mutex有所有者,Semaphore没有。-计数:Mutex是二进制(0或1),Semaphore可以计数。-使用场景:基于引用解释。3.**使用场景**:给出具体例子。4.**生成相关问题**:在末尾添加相关问题。5.**引用标识**:在引用段落末尾添加[^n]。确保使用中文。行内数学表达式:如果有数学内容,比如计数,可以用$...$。例如,Semaphore的计数可以是$n$,但问题中没有太多数学,所以可能少用。独立公式:如果没有复杂公式,可能不需要,但如果有,单独成段。LaTeX语法正确:例如,$x^2$。现在,构建回答大纲:-开头:直接回答问题。-主体:-Mutex的定义和特点。-Semaphore的定义和特点。-关键区别:基于引用[^1]和[^4],强调目的不同。-Mutex用于互斥访问共享资源。-Semaphore用于控制访问资源的线程数。-所有权:Mutex必须由锁定它的线程解锁(引用[^4])。-计数:Mutex是二进制,Semaphore可以有多个(引用[^3]和[^4])。-使用场景:-Mutex:保护临界区,如共享变量(引用[^4]示例)。-Semaphore:生产者-消费者问题,限制资源访问(引用[^2]和[^4])。-跨进程:Mutex可以命名用于跨进程(引用[^5]),Semaphore也可以但较少。-结尾:总结,并生成相关问题。生成相关问题:类似示例,3-4个问题,如:1.Mutex和Semaphore在性能上有什么差异?2.如何实现一个Semaphore?3.在分布式系统中如何使用同步机制?引用标识:在描述引用内容时添加。例如,“Mutex和Semaphore的目的不同,可能由于实现相似而被混淆[^1]。”确保回答真实可靠:基于标准知识,引用提供的信息。</think>### Mutex与Semaphore的区别及使用场景 Mutex(互斥锁)和Semaphore信号量)是多线程编程中常用的同步机制,用于协调线程对共享资源的访问,防止竞态条件。它们虽然表面上类似(例如,Mutex有时被误称为二进制信号量[^1]),但设计目的、实现和行为有本质区别。以下我将逐步解析它们的区别,并给出典型使用场景,帮助您清晰理解。 #### 1. **基本定义与核心区别** - **Mutex(互斥锁)**: - Mutex是二进制锁,状态为“锁定”或“解锁”($0$ 或 $1$),用于确保一次只有一个线程访问共享资源。Mutex具有“所有权”概念:只有锁定它的线程才能解锁它[^4]。这防止了其他线程误释放锁,从而避免死锁。例如,在Linux内核中,`mutex_lock`函数获取锁时,如果失败,线程会休眠等待[^3]。 - 数学表示:Mutex的状态可建模为二元变量 $s \in \{0, 1\}$,其中 $s=1$ 表示资源可用。 - **Semaphore信号量)**: - Semaphore是一个计数器,允许指定数量的线程同时访问资源(计数范围 $n \geq 0$)。它没有所有权概念,任何线程都可以释放信号量信号量常用于控制资源池(如数据库连接)[^4]。例如,初始值为 $k$ 的信号量表示最多 $k$ 个线程可并发访问。 - 数学表示:信号量计数 $c$ 满足 $c \geq 0$,操作基于 $P$(等待,减少计数)和 $V$(发信号,增加计数)原语。 关键区别总结: - **所有权**:Mutex要求锁的持有者解锁(线程绑定),而Semaphore可由任何线程释放[^4]。 - **计数**:Mutex是二进制($0/1$),Semaphore支持多计数($n \geq 0$)[^3][^4]。 - **目的**:Mutex专为互斥访问(保护临界区),Semaphore用于协调资源访问(如限制并发线程数)[^1][^2]。 - **实现复杂度**:Mutex通常更轻量,适合简单互斥;Semaphore更灵活,但可能引入更多开销。 #### 2. **使用场景分析** 基于实际应用,Mutex和Semaphore各有适用场景: - **Mutex的使用场景**: - **保护共享变量**:当多个线程需读写同一变量时(如计数器),使用Mutex确保原子操作。例如,在C语言中: ```c pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); pthread_mutex_lock(&mutex); // 临界区:共享变量操作,如 num++ pthread_mutex_unlock(&mutex); ``` 这防止了数据竞争[^4]。 - **跨进程同步**:命名Mutex可用于不同进程间的互斥(如C#中的`Mutex mutex = new Mutex(false, "GlobalMutex")`),适用于分布式系统资源协调[^5]。 - 典型场景:文件写入、全局配置更新。 - **Semaphore的使用场景**: - **资源池管理**:当资源有限(如线程池或数据库连接)时,Semaphore控制最大并发数。例如,初始值为$5$的信号量允许最多5个线程同时访问。 - **生产者-消费者问题**:Semaphore协调生产者和消费者线程,通过两个信号量(一个表示缓冲区空位,一个表示数据项数)实现高效同步[^2][^4]。 - 典型场景:Web服务器请求处理、多线程下载。 为什么选择? - 用Mutex当需要严格互斥(如银行账户转账)。 - 用Semaphore当需允许多个线程共享资源(如打印机队列)[^2]。 #### 3. **注意事项** - **死锁风险**:Mutex的所有权特性减少了误解锁风险,但不正确使用(如嵌套锁)仍可导致死锁。Semaphore的计数机制在资源竞争激烈时可能引发优先级反转问题[^1]。 - **性能**:在单资源场景,Mutex通常更快(因轻量级);Semaphore在复杂协调中更高效[^3]。 - **跨平台**:Mutex(如C#的`Mutex`类)支持跨进程;Semaphore也可实现,但需额外配置[^5]。 总之,Mutex和Semaphore的核心区别在于所有权和计数机制:Mutex是“独占锁”,Semaphore是“资源门”。正确选择能提升并发程序可靠性[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值