了解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)则其值为最后一个元素值,如果是最后一个,那么其值为第一个元素 ...
随机推荐
- 洛谷P4009 汽车加油行驶问题(分层最短路)
传送门 说好的网络流24题呢……上次是状压dp,这次怎么又最短路了…… 不过倒是用这题好好学了一下分层图最短路 把每一个位置$(x,y)$,油量剩余$k$表示为一个状态,然后转化成一个$n$进制数,这 ...
- [Swift]八大排序算法(八):基数排序
排序分为内部排序和外部排序. 内部排序:是指待排序列完全存放在内存中所进行的排序过程,适合不太大的元素序列. 外部排序:指的是大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存 ...
- 从图片中提取html格式的布局
制作界面的自动化 意义:对于程序设计人员来说,比较痛苦的是制作界面.从设计人员发送的稿件(通常为jpg格式),到完成html的布局,需要将图像转化为div标记,自动化这一过程十分有意义 1.设计一个工 ...
- 初用sqlite3.exe
1.记得要先建立数据库文件 为了进行数据库的编写,我安装了sqlite3,由于刚接触数据库,我尝试着建立表,并插入元组,属性,用select from语句也可以调出写入的内容,但是不知道如何保存,直接 ...
- 《Java并发编程实战》第十章 避免活跃性危急 读书笔记
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/love_world_/article/details/27635333 一.死锁 所谓死锁: 是指两 ...
- ScrollView-电影列表
ScrollView 的使用import React, { Component } from 'react';import { Platform, StyleSheet, Text, View, Sc ...
- flask中的蓝图 Blueprint
模块化 随着flask程序越来越复杂,我们需要对程序进行模块化的处理,之前学习过python的模块化管理,于是针对一个简单的flask程序进行模块化处理 简单来说,Blueprint 是一个存储操作方 ...
- numpy-1
NumPy数组 NumPy数组是一个多维数组对象,称为ndarray.其由两部分组成: 实际的数据 描述这些数据的元数据 大部分操作仅针对于元数据,而不改变底层实际的数据. 关于NumPy数组有几点必 ...
- 精神AC合集 2018.4.3
UESTC炸了,先把看似十分OK(只是过了样例)的代码贴上,修复好后再交上去 594 #include<iostream> #include<algorithm> #inclu ...
- Randy Pausch’s Last Lecture
he University of Virginia American Studies Program 2002-2003. Randy Pausch ...