[一个经典的多线程同步问题]解决方案三:互斥量Mutex
本篇通过互斥量来解决线程的同步,学习其中的一些知识。
互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问。互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源。使用互斥量Mutex主要将用到四个函数。下面是这些函数的原型和使用说明。
第一个 CreateMutex
函数功能:创建互斥量(注意与事件Event的创建函数对比)
函数原型:
HANDLECreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
函数说明:
第一个参数表示安全控制,一般直接传入NULL。
第二个参数用来确定互斥量的初始拥有者。
如果传入TRUE表示互斥量对象内部会记录创建它的线程的线程ID号并将递归计数设置为1,
由于该线程ID非零,所以互斥量处于未触发状态。
如果传入FALSE,那么互斥量对象内部的线程ID号将设置为NULL,递归计数设置为0,
这意味互斥量不为任何线程占用,处于触发状态。
第三个参数用来设置互斥量的名称,在多个进程中的线程就是通过名称来确保它们访问的是同一个互斥量。
函数访问值:
成功返回一个表示互斥量的句柄,失败返回NULL。
注意区分触发状态和非触发状态:触发状态就是线程不会在这里阻塞,或者由原来的不可用变为了可用,去唤醒等待的线程。
第二个打开互斥量
函数原型:
HANDLEOpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName //名称
);
函数说明:
第一个参数表示访问权限,对互斥量一般传入MUTEX_ALL_ACCESS。详细解释可以查看MSDN文档。
第二个参数表示互斥量句柄继承性,一般传入TRUE即可。
第三个参数表示名称。某一个进程中的线程创建互斥量后,其它进程中的线程就可以通过这个函数来找到这个互斥量。
函数访问值:
成功返回一个表示互斥量的句柄,失败返回NULL。
第三个触发互斥量
函数原型:
BOOLReleaseMutex (HANDLEhMutex)
函数说明:
访问互斥资源前应该要调用等待函数,结束访问时就要调用ReleaseMutex()来表示自己已经结束访问,
其它线程可以开始访问了。
最后一个清理互斥量
由于互斥量是内核对象,因此使用CloseHandle()就可以(这一点所有内核对象都一样)。
接下来我们就在经典多线程问题用互斥量来保证主线程与子线程之间的同步,由于互斥量的使用函数类似于事件Event,所以可以仿照上一篇的实现来写出代码:
#include <stdio.h>
#include <process.h>
#include <windows.h> int g_num;
//互斥量和关键段
HANDLE g_hThreadParam;
CRITICAL_SECTION g_csThreadCode;
const int THREAD_NUM = 10; unsigned int __stdcall Fun(void *param)
{
int n_thread_num = *(int*)param;
//触发互斥量
ReleaseMutex(g_hThreadParam); Sleep(50); EnterCriticalSection(&g_csThreadCode);
g_num++;
Sleep(0);
printf("线程编号为%d 全局变量值为%d\n", n_thread_num, g_num);
LeaveCriticalSection(&g_csThreadCode); return 0;
} int main(void)
{
//初始化互斥量与关键段 第二个参数为true表示互斥量为创建线程所有
g_hThreadParam = CreateMutex(NULL, false, NULL);
InitializeCriticalSection(&g_csThreadCode); HANDLE handle[THREAD_NUM];
int i = 0;
g_num = 0;
while (i < THREAD_NUM)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
//在访问互斥资源前,调用等待函数,等待互斥量被触发
WaitForSingleObject(g_hThreadParam, INFINITE);
++i;
} WaitForMultipleObjects(THREAD_NUM, handle, true, INFINITE);
//销毁互斥量和关键段
CloseHandle(g_hThreadParam);
DeleteCriticalSection(&g_csThreadCode);
for (i = 0; i < THREAD_NUM; i++)
{
CloseHandle(handle[i]);
} return 0;
}
执行结果图:

可以看出,与关键段类似,互斥量也是不能解决线程间的同步问题。
联想到关键段会记录线程ID即有“线程拥有权”的,而互斥量也记录线程ID,莫非它也有“线程拥有权”这一说法。
答案确实如此,互斥量也是有“线程拥有权”概念的。“线程拥有权”在关键段中有详细的说明,这里就不再赘述了。另外由于互斥量常用于多进程之间的线程互斥,所以它比关键段还多一个很有用的特性——“遗弃”情况的处理。比如有一个占用互斥量的线程在调用ReleaseMutex()触发互斥量前就意外终止了(相当于该互斥量被“遗弃”了),那么所有等待这个互斥量的线程是否会由于该互斥量无法被触发而陷入一个无穷的等待过程中了?这显然不合理。因为占用某个互斥量的线程既然终止了那足以证明它不再使用被该互斥量保护的资源,所以这些资源完全并且应当被其它线程来使用。因此在这种“遗弃”情况下,系统自动把该互斥量内部的线程ID设置为0,并将它的递归计数器复置为0,表示这个互斥量被触发了。然后系统将“公平地”选定一个等待线程来完成调度(被选中的线程的WaitForSingleObject()会返回WAIT_ABANDONED_0)。
下面写二个程序来验证下:
第一个程序创建互斥量并等待用户输入后就触发互斥量。第二个程序先打开互斥量,成功后就等待并根据等待结果作相应的输出:
第一个进程代码:
#include <stdio.h>
#include <conio.h>
#include <windows.h> const char mutex_name[] = "MUTEX_NAME"; int main(void)
{
HANDLE hMutex = CreateMutex(NULL, true, mutex_name);
printf("互斥量已经创建,显现按任意键触发互斥量\n");
getch();
exit(0); ReleaseMutex(hMutex);
printf("互斥量已经触发"); CloseHandle(hMutex);
return 0;
}
第二个进程代码:
#include <stdio.h>
#include <windows.h> const char mutex_name[] = "MUTEX_NAME";
int main()
{
HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, true, mutex_name);
if (hMutex == NULL)
{
printf("打开互斥量失败\n");
return 0;
} printf("等待中...\n");
DWORD dwResult = WaitForSingleObject(hMutex, INFINITE);//等待互斥量被触发
switch (dwResult)
{
case WAIT_ABANDONED:
printf("拥有互斥量的进程意外终止\n");
break;
case WAIT_OBJECT_0:
printf("已经收到信号\n");
break;
case WAIT_TIMEOUT:
printf("信号未在规定的时间内送到\n");
break;
} CloseHandle(hMutex); return 0;
}
运用这二个程序时要先启动程序一再启动程序二。下面展示部分输出结果:
结果一:两个进程顺利执行完毕:

结果二:将进程一中//exit(0);前面的注释符号去掉,这样程序一在触发互斥量之前就会因为执行exit(0);语句而且退出,程序二会收到WAIT_ABANDONED消息并输出“拥有互斥量的进程意外终止”:

有这个对“遗弃”问题的处理,在多进程中的线程同步也可以放心的使用互斥量。
最后总结下互斥量Mutex:
1.互斥量是内核对象,它与关键段都有“线程所有权”所以不能用于线程的同步。
2.互斥量能够用于多个进程之间线程互斥问题,并且能完美的解决某进程意外终止所造成的“遗弃”问题。
[一个经典的多线程同步问题]解决方案三:互斥量Mutex的更多相关文章
- [一个经典的多线程同步问题]解决方案一:关键段CS
前面提出了一个经典的多线程同步互斥问题,本篇将用关键段CRITICAL_SECTION来尝试解决这个问题. 本文先介绍如何使用关键段,然后再深层次的分析下关键段的实现机制和原理. 关键段CRITICA ...
- [一个经典的多线程同步问题]解决方案二:Event事件
使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权”特性所以关键段只能用于线程的互斥而不能用于同步.本篇介绍用事件Event来尝试解决这个线程同步问题. 首先介绍下如何使用事件.事件E ...
- C#线程同步(3)- 互斥量 Mutex
文章原始出处 http://xxinside.blogbus.com/logs/47162540.html 预备知识:C#线程同步(1)- 临界区&Lock,C#线程同步(2)- 临界区&am ...
- 转--- 秒杀多线程第七篇 经典线程同步 互斥量Mutex
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...
- 经典线程同步 互斥量Mutex
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...
- (转)经典线程同步 互斥量Mutex
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...
- 多线程面试题系列(7):经典线程同步 互斥量Mutex
前面介绍了关键段CS.事件Event在经典线程同步问题中的使用.本篇介绍用互斥量Mutex来解决这个问题. 互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问.互斥量与关键段的行为非常相似, ...
- 秒杀多线程第七篇 经典线程同步 互斥量Mutex
本文转载于:http://blog.csdn.net/morewindows/article/details/7470936 前面介绍了关键段CS.事件Event在经典线程同步问题中的使用.本篇介绍用 ...
- [转]一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程
一个简单的Linux多线程例子 带你洞悉互斥量 信号量 条件变量编程 希望此文能给初学多线程编程的朋友带来帮助,也希望牛人多多指出错误. 另外感谢以下链接的作者给予,给我的学习带来了很大帮助 http ...
随机推荐
- android开发 不注意的异常
近期刚上班,做了一个应用,要上线.測试天天測试,天天有bug,个人总结了几个常常忽略的地方: 1:在 继承 BaseAdapter 的 @Override public int getCount() ...
- Elasticsearch的javaAPI之percolator
Elasticsearch的javaAPI之percolator percolator同意一个在index中注冊queries,然后发送包括doc的请求,返回得到在index中注冊过的而且匹配doc的 ...
- 小心ThreadLocal的陷阱
ThreadLocal用在多线程时保存线程级的局部变量,当我们需要在线程内共享数据时,ThreadLocal屡试不爽,但是ThreadLocal也会有一个问题,当你使用线程池时,线程可能会被重用,所以 ...
- .NET 创建并写CSV文件
/// <summary> /// 创建并写日志 /// </summary> /// <param name="SuccessA100">&l ...
- UVA 1617 Laptop
题意: 有n条长度为1的线段,确定他们的起点,必须是整数,使得第i条线段在[ri,di]之间.最后输出空隙的最小值 分析: 原始数据排序,排序的规则是先按照右端点排序,右端点相同的情况下,再按照左端点 ...
- JS删除练习
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...
- C# 7 函数 青歌赛打分 天气预报
函数: 数据类型--变量常量--运算符表达式--语句(顺序,分支,循环)--数组--函数 程序里的函数:能完成一个相对独立功的代码块. 数学里的函数:高度抽象. 函数四要素:函数名,输入,输出,加工 ...
- js学习日记 (1)createDocumentFragment() ES6 => 箭头
只能说是会用和记载,深入理解还需时间. 有关性能优化: 使用createdocumentfragment()方法可以创建某个具有节点该有的所有属性的节点. 使用情况: 提取文档中的某个小部分,修改文 ...
- Boost.Python:使用继承
An example #include <boost/python.hpp> #include <memory> #include <iostream> using ...
- JavaScript基本概念(操作符)
一元操作符 一元操作符在处理所有的非数值时,相当于将该值经过Number()转换成数值,如 +"12" 将把 "12" 字符串转换为数字. 位操作符 负数在计算 ...