了解WaitForSingleObject中WAIT_ABANDONED 返回值
1、互斥量内核对象
互斥量内核对象用来确保一个线程独占对一个资源的访问。互斥量对象包含一个使用计数、线程ID以及递归计数。互斥量与关键段的行为完全相同。但是互斥量是内核对象,而关键段是用户模式下的同步对象。这意味着互斥量比关键段慢。但这同时意味着不同进程中的线程可以访问同一互斥量,还意味着线程可以在等待对资源的访问权的同时指定一个最长等待时间。
线程ID用来标识当前占用这个互斥量的是系统中的哪个线程,递归计数表示这个线程占用该互斥量的次数。互斥量一般用来对多个线程访问同一块内存进行保护。它可以确保正在访问的内存块的任何线程会独占内存块的访问权。
互斥量的规则:
- 如果线程ID为0(无效线程ID),那么该互斥量不为任何线程所占用,它处于触发状态。
- 如果线程ID为非零值,那么有一个线程已经占用了该信号量,它处于未触发状态。
- 于所有其他内核对象不同,操作系统对互斥量进行了特殊处理,允许它们违反一些常规规则。
要使用互斥量,进程必须先调用CreateMutex来创建一个互斥量
HANDLE CreateMutex(
__in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes,
__in BOOL bInitialOwner,
__in_opt LPCSTR lpName
);
参数bInitialOwner用来控制互斥量的初始状态。如果传的是FALSE(通常情况) 那么互斥量对象的线程ID和递归计数都被设为0。这意味着互斥量不为任何线程所占用,因此处于触发状态。
如果该参数为TRUE,那么对象的线程ID被设定为调用线程的线程ID,递归计数将被设定为1。由于线程ID为非零值,因此互斥量最初处于未触发状态。
下面就正常情况给出一个例子说明互斥量的常规使用方法:(例子在VS2010下编译运行)
首先编写main函数
#include <stdio.h>
#include <Windows.h>
#include <process.h> int main(int argc, char* argv[])
{
HANDLE hMutex = NULL;
hMutex = CreateMutexA(NULL,FALSE,"test1234qwer");
HANDLE hThread1 = CreateThread(NULL, , LisDevProc1, (LPVOID)&hMutex, , NULL);
HANDLE hThread2 = CreateThread(NULL, , LisDevProc2, (LPVOID)&hMutex, , NULL); Sleep(); if(hThread1)
CloseHandle( hThread1 ); if(hThread2)
CloseHandle( hThread2 ); CloseHandle(hMutex); system("pause");
return ;
}
该函数创建了1个互斥量及两个线程,然后等待10s释放相关资源结束。
DWORD WINAPI LisDevProc1(LPVOID para)
{
HANDLE* phMutex = (HANDLE*)para; WaitForSingleObject(*phMutex,INFINITE); printf("Enter Thread1\n");
printf("I'm sleeping……\n"); Sleep(); printf("Leave Thread1\n"); ReleaseMutex(*phMutex);
return ;
}
上述是线程1的处理函数,先等待该互斥量然后sleep3秒然后释放该互斥量。
线程2处理函数跟线程1相同,如下
DWORD WINAPI LisDevProc2(LPVOID para)
{
HANDLE* phMutex = (HANDLE*)para; WaitForSingleObject(*phMutex,INFINITE); printf("Enter Thread2\n");
printf("I'm sleeping……\n"); Sleep(); printf("Leave Thread2\n"); ReleaseMutex(*phMutex);
return ;
}
执行结果:

执行顺序是:线程1先通过WaitForSingleObject获取互斥量的所有权,打印Enter,然后等待3秒,打印Leave,最后释放互斥量的所有权,然后线程2才获取到互斥量的所有权。。。可以看到,互斥量确实实现了对共享资源的保护。
上边还提到了一条特殊的规则,
线程在试图等待一个未触发的互斥量对象时,通常线程会进入等待状态。但是,系统会检查想要获得互斥量的线程的线程ID于互斥量内部记录的线程ID是否相同,如果线程ID一致,那么系统会让线程保持可调度状态——即使该互斥量尚未触发。每次线程成功的等待了一个互斥量,互斥量的递归计数会递增。使递归计数大于1的唯一途径就是利用这一例外,让线程多次等待同一互斥量。
对上述特例,先把以上代码中线程1的代码改为如下:
DWORD WINAPI LisDevProc1(LPVOID para)
{
HANDLE* phMutex = (HANDLE*)para; WaitForSingleObject(*phMutex,INFINITE);
WaitForSingleObject(*phMutex,INFINITE);
printf("Enter Thread1\n");
printf("I'm sleeping……\n"); Sleep(); printf("Leave Thread1\n");
ReleaseMutex(*phMutex);
ReleaseMutex(*phMutex);
return ;
}
可以看到,线程1的处理函数调用了两次WaitForSingleObject,结果跟之前结果一样,印证了该特例确实存在。
以上说来了这么多,才刚刚进入主题,讨论一下遗弃问题。
互斥量的这种线程所有权的概念导致出现遗弃问题。
如果占用互斥量的线程在释放互斥量之前终止(使用ExitThread,TerminateThread,ExitProcess,TerminateProcess)那么对于互斥量和正在等待该互斥量的线程来说会发生什么情况?答案是系统会认为互斥量被遗弃(abandoned),因为占用它的线程已经终止,因此无法释放它。
因为系统会记录所有的互斥量和线程内核对象,因此它确切的知道互斥量何时被遗弃。当互斥量被遗弃的时候,系统会自动将互斥量的线程ID设为0,将它的递归计数设为0。然后系统检查有没有其他线程正在等待该互斥量。如果有,那么系统会公平的选择一个正在等待的线程,把对象内部的线程Id设为所选择的那个线程的线程ID,并将递归计数设为1,这样被选择的线程就变成可调度状态了。
一旦检测到某互斥量被检测到,则WaitForSingleObject返回的不是WAIT_OBJECT_0,而是一个特殊值WAIT_ABANDONED。
返回该值,说明等待的互斥量被某个线程遗弃,同时说明被保护的资源已经被破坏了。这种情况下,写的程序自己必须决定该怎么做。
看下一下程序代码:
#include <stdio.h>
#include <Windows.h>
#include <process.h> DWORD WINAPI LisDevProc1(LPVOID para)
{
HANDLE* phMutex = (HANDLE*)para; WaitForSingleObject(*phMutex,INFINITE);
printf("Enter Thread1\n");
printf("I'm sleeping……\n"); Sleep(); printf("Leave Thread1\n");
ReleaseMutex(*phMutex);
return ;
} DWORD WINAPI LisDevProc2(LPVOID para)
{ HANDLE* phMutex = (HANDLE*)para;
int ret;
int flag;
do{
flag = ;
ret = WaitForSingleObject(*phMutex,INFINITE);
switch(ret)
{
case WAIT_OBJECT_0:
printf("normal ....\n");
break;
case WAIT_ABANDONED:
flag = ;
printf("abandoned ....\n");
break;
}
}while(flag);
printf("Enter Thread2\n");
printf("I'm sleeping……\n"); Sleep(); printf("Leave Thread2\n");
ReleaseMutex(*phMutex);
return ;
} int main(int argc, char* argv[])
{
HANDLE hMutex = NULL;
hMutex = CreateMutexA(NULL,FALSE,"test1234qwer");
HANDLE hThread1 = CreateThread(NULL, , LisDevProc1, (LPVOID)&hMutex, , NULL);
HANDLE hThread2 = CreateThread(NULL, , LisDevProc2, (LPVOID)&hMutex, , NULL); Sleep();
TerminateThread(hThread1, ); Sleep(); if(hThread1)
CloseHandle( hThread1 ); if(hThread2)
CloseHandle( hThread2 ); CloseHandle(hMutex); system("pause");
return ;
}
在main函数中,创建了2个线程后的1.5秒 执行了一句TerminateThread结束了线程1,线程1没来得及释放互斥量就挂掉了,(慎重用TerminateThread等函数)看下结果如下:

可以看到在杀死线程1后,线程2的WaitForSingleObject立刻返回WAIT_ABANDONED,然后线程2再次WaitForSingleObject时又立刻返回WAIT_OBJECT_0
最后,一定注意遗弃问题的产生,如果产生,说明受保护的共享数据可能已经被破坏掉了。
了解WaitForSingleObject中WAIT_ABANDONED 返回值的更多相关文章
- Asp.net MVC 中Controller返回值类型ActionResult
		[Asp.net MVC中Controller返回值类型] 在mvc中所有的controller类都必须使用"Controller"后缀来命名并且对Action也有一定的要求: 必 ... 
- [改善Java代码]不要在finally块中处理返回值
		在finally代码块中处理返回值,这是在面试题中经常出现的题目.但是在项目中绝对不能再finally代码块中出现return语句,这是因为这种处理方式非常容易产生"误解",会严重 ... 
- Controller 中Action 返回值类型  及其 页面跳转的用法
		•Controller 中Action 返回值类型 View – 返回 ViewResult,相当于返回一个View 页面. -------------------------------- ... 
- robot framework中的返回值
		1.若想要再setup中有返回值,给后续的操作使用 A)在setup的关键词中需要的返回值,设置为global variable或者suit variable:如下图:但是在编译器中,会报错,但是执行 ... 
- Web API中的返回值类型
		WebApi中的返回值类型大致可分为四种: Void/ IHttpActionResult/ HttpResponseMessage /自定义类型 一.Void void申明方法没有返回值,执行成功后 ... 
- c++中带返回值函数没写return能通过编译但运行时会出现奇怪问题
		c++中带返回值函数没写return能通过编译但运行时会出现奇怪问题 例如: string myFunc(){ theLogics(); } 发现调用: myFunc(); 崩溃. 但调用: cout ... 
- try--catch--finally中return返回值执行的顺序(区别)
		1.try块中没有抛出异常,try.catch和finally块中都有return语句 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static int ... 
- C++ 中的返回值
		C++中大致有三种返回值:值拷贝(副本),值引用和指针,返回什么类型的值要根据当时情况而定. 如果返回的是大型对象的副本,那么在每一次的函数调用后返回,都会调用该对象类型的拷贝构造函数构造一个新的副本 ... 
- javascript学习笔记-2:jQuery中$("xx")返回值探究
		最近在写一个jQuery插件的时候,需要用到一个条件: 一组img标签,每一个元素都需要被它前面的元素值src替换,如果是第一个(序列为0)则其值为最后一个元素值,如果是最后一个,那么其值为第一个元素 ... 
随机推荐
- 用python实现按权重对N个数据进行选择
			需求:某公司有N个人,根据每个人的贡献不同,按贡献值给每个人赋予一个权重.设计一种算法实现公平的抽奖. 需求分析:按照权重对数据进行选择. 代码实现: 1 def fun(n,p): 2 " ... 
- [Swift实际操作]九、完整实例-(3)创建和安装开发证书、发布证书及开发证书配置文件、发布证书配置文件
			本文将为你演示,如何创建开发证书和发布证书,以及其他辅助内容.首先打开浏览器,进入[苹果开发者网站]输入[Apple ID]和[密码],点击登录按钮,进入开发者管理后台. 点击左侧的[Membersh ... 
- python2和python3 分别连接MySQL的代码
			python2中的写法如下: #coding=utf-8 import MySQLdb try: conn = MySQLdb.connect(host='localhost', port=3306, ... 
- 【智能算法】迭代局部搜索(Iterated Local Search, ILS)详解
			迭代局部搜索(Iterated Local Search, ILS) 源代码下载请关注微信公众号[程序猿声],在后台回复:[ILS],不包括[]即可下载. 00 目录 局部搜索算法 简单局部搜索 迭代 ... 
- Python3之uuid模块
			一. 简介 UUID是128位的全局唯一标识符,通常由32字节的字母串表示.它可以保证时间和空间的唯一性,也称为GUID. 全称为:UUID--Universally Unique IDentifie ... 
- nRF51822外设应用[2]:GPIOTE的应用-按键检测
			版权声明:本文为博主原创文章,转载请注明作者和出处. 作者:强光手电[艾克姆科技-无线事业部] 1. nRF51822寄存器类型 nRF51822的寄存器和一般的单片机有所差别,nRF51822 ... 
- Spring学习笔记(二)——Spring相关配置&属性注入&Junit整合
			一.Spring的相关配置 1.1 Bean元素 class属性:被管理对象的完整类名 name属性:给Bean起个名字,能重复,能使用特殊字符.后来属性 id属性:给Bean起个名字,不能重复,不能 ... 
- mysql 查询库中有几张表
			SELECT COUNT(*) TABLES, table_schemaFROM information_schema.TABLESWHERE table_schema = '999*999' 999 ... 
- chrome  相关设置.
			1. 使用chrome 添加道桌面的快捷方式,自动打开两个 tab 解决方法: chrome://apps/ 右击 你的应用图标..勾选在新窗口打开. 
- [原创]c# 类中 Collection 字段初始化的特殊之处
			1.今天看一下StackExchange.Redis的源代码,里面有这样一段代码 public sealed class ConfigurationOptions : ICloneable { ... ... 
