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 返回值的更多相关文章

  1. Asp.net MVC 中Controller返回值类型ActionResult

    [Asp.net MVC中Controller返回值类型] 在mvc中所有的controller类都必须使用"Controller"后缀来命名并且对Action也有一定的要求: 必 ...

  2. [改善Java代码]不要在finally块中处理返回值

    在finally代码块中处理返回值,这是在面试题中经常出现的题目.但是在项目中绝对不能再finally代码块中出现return语句,这是因为这种处理方式非常容易产生"误解",会严重 ...

  3. Controller 中Action 返回值类型 及其 页面跳转的用法

        •Controller 中Action 返回值类型 View – 返回  ViewResult,相当于返回一个View 页面. -------------------------------- ...

  4. robot framework中的返回值

    1.若想要再setup中有返回值,给后续的操作使用 A)在setup的关键词中需要的返回值,设置为global variable或者suit variable:如下图:但是在编译器中,会报错,但是执行 ...

  5. Web API中的返回值类型

    WebApi中的返回值类型大致可分为四种: Void/ IHttpActionResult/ HttpResponseMessage /自定义类型 一.Void void申明方法没有返回值,执行成功后 ...

  6. c++中带返回值函数没写return能通过编译但运行时会出现奇怪问题

    c++中带返回值函数没写return能通过编译但运行时会出现奇怪问题 例如: string myFunc(){ theLogics(); } 发现调用: myFunc(); 崩溃. 但调用: cout ...

  7. 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 ...

  8. C++ 中的返回值

    C++中大致有三种返回值:值拷贝(副本),值引用和指针,返回什么类型的值要根据当时情况而定. 如果返回的是大型对象的副本,那么在每一次的函数调用后返回,都会调用该对象类型的拷贝构造函数构造一个新的副本 ...

  9. javascript学习笔记-2:jQuery中$("xx")返回值探究

    最近在写一个jQuery插件的时候,需要用到一个条件: 一组img标签,每一个元素都需要被它前面的元素值src替换,如果是第一个(序列为0)则其值为最后一个元素值,如果是最后一个,那么其值为第一个元素 ...

随机推荐

  1. clickonce发布方式创建桌面快捷方式

    1.工程属性->发布->选项->清单:创建桌面快捷方式打勾 2.工程属性->应用程序->清单:下拉列表选择Properties\app.manifest(其中的图标可以选 ...

  2. Django + Gunicorn + Nginx 部署 Ubuntu 服务器

    Django + Gunicorn + Nginx 部署服务器 获取腾讯云 root权限 本人的服务器使用的是腾讯云,腾讯云默认是没有开放 root 用户的,我们来创建 root 用户. 创建 roo ...

  3. 【bzoj4709】[Jsoi2011]柠檬 决策单调性+dp

    Description Flute 很喜欢柠檬.它准备了一串用树枝串起来的贝壳,打算用一种魔法把贝壳变成柠檬.贝壳一共有 N (1 ≤ N ≤ 100,000) 只,按顺序串在树枝上.为了方便,我们从 ...

  4. 题解 P3252 【[JLOI2012]树】

    \(\Huge{[JLOI2012]树}\) 题目描述 在这个问题中,给定一个值S和一棵树.在树的每个节点有一个正整数,问有多少条路径的节点总和达到S.路径中节点的深度必须是升序的.假设节点1是根节点 ...

  5. opencv学习笔记(四)--图像平滑处理

    图像平滑处理的几种常用方法: 均值滤波 归一化滤波 高斯模糊 中值滤波 平滑处理(模糊)的主要目的是去燥声: 不同的处理方式适合不同的噪声图像,其中高斯模糊最常用. 其实最重要的是对图像卷积的核的理解 ...

  6. 收藏的一些有意思的CSS加载样式

    先看下效果 全部代码贴出,自己粘贴调试测试: <!DOCTYPE html> <html lang="en"> <head> <meta ...

  7. HDU-2063(二分图匹配模板题)

    过山车Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submissi ...

  8. Luogu P1156 垃圾陷阱 DP

    f[i][j]表示在第i个垃圾,高度为j的最大生命值 转移分三部分: 如果j>=当前垃圾的高度,且两个垃圾间的时间小于等于上一个状态f[i-1][j-a[i].v]的生命值,则可以垫高度 如果j ...

  9. CH2401 送礼物 双向搜索

    双向搜索:把前一半的可行状态搜出来,然后sort+unique,之后搜后一半时,结束时二分一下前一半的答案,拼出一个与W尽量接近的ans来更新 ps:距LYD说前一半取n/2+2时跑的最快...不知, ...

  10. QQ互联 网站应用接入

    1.准备工作 接入qq登陆前,网站需要先进行申请,获得对应的appid和appkey, 以保证后续流程中可正确对网站与用户进行授权. 2.放置qq登陆按钮. 在网站页面上放置“qq登录”按钮,并为按钮 ...