第9章 用内核对象进行线程同步(3)_信号量(semaphore)、互斥量(mutex)

9.5 信号量内核对象(Semaphore)

(1)信号量的组成

  ①计数器:该内核对象被使用的次数

  ②最大资源数量:标识信号量可以控制的最大资源数量(带符号的32位)

  ③当前资源数量:标识当前可用资源的数量(带符号的32位)

(2)信号量的使用规则

  ①如果当前资源计数>0,那么信号量处于触发状态,表示有可用资源

  ②如果当前资源计数=0,那么信号量处于未触发状态,表示没有可用资源

  ③系统绝不会让当前资源计数变为负数;

  ④当前资源计数绝对不会大于最大资源计数

(3)信号量的用法

(4)相关函数

  ①创建信号量CreateSemaphore


参数


描述


psa


安全属性


lInitialCount


初始化时,共有多少个资源是可用的。如果该值设为0,表示没有可用资源,此时信号量为未触发状态,任何等待信号数的线程将进入等待状态。以后可通过调用ReleaseSemaphore来增加可用资源,同时变为触发状态。


LMaximumCount


能够处理的最大的资源数量


pszName


信号量的名称

  ②增加信号量:ReleaseSemaphore


参数


描述


hSemaphore


信号量句柄


lReleaseCount


将lReleaseCount值加到信号量的当前资源计数上


pLPreviousCount


返回当前资源计数的原始值,一般填NULL

  ★调用ReleaseSemaphore可以获得资源计数的原始值,但同时会增加当前资源的计数值。目前还没有办法在不改变当前资源计数值的前提下获得信号量的可用资源数。(即使lReleaseCount填入0也获取不到!)

  ③Wait*之类的等待函数:当线程调用等待函数时,如果信号量处于触发状态(可用资源大于0),则线程获得信号量的一个资源并把可用资源数量减1,同时继续执行。如果信号量处于未触发状态,则线程进入等待状态。直到其他线程释放资源。

【Semaphore程序】

#include <windows.h>
#include <tchar.h>
#include <locale.h>

//////////////////////////////////////////////////////////////////////////
//一个只能容纳10个客人的餐馆来了12个客户
#define MAX_SEM_COUNT   10
#define THREADCOUNT     12

//////////////////////////////////////////////////////////////////////////
HANDLE  g_hSemaphore = NULL;
DWORD  WINAPI ThreadProc(LPVOID pvParam);

//////////////////////////////////////////////////////////////////////////
int _tmain()
{

    _tsetlocale(LC_ALL, _T("chs"));

    HANDLE aThread[THREADCOUNT] = { NULL };
    DWORD dwThreadID = 0;

    g_hSemaphore = CreateSemaphore(NULL, MAX_SEM_COUNT, MAX_SEM_COUNT, NULL);
    //以下两句与上面那句的实现的效果一样,先创建无资源的信号量,再增加资源
    //g_hSemaphore = CreateSemaphore(NULL, 0, MAX_SEM_COUNT, NULL);
    //ReleaseSemaphore(g_hSemaphore, MAX_SEM_COUNT, NULL);

    if (NULL == g_hSemaphore){
        _tprintf(_T("创建信号量失败:%d\n"), GetLastError());
        return -1;
    }

    //创建12个线程
    for (int i = 0; i < THREADCOUNT;i++){
        aThread[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc,
                                NULL,0,&dwThreadID);
        if (aThread[i] == NULL){
            _tprintf(_T("创建线程失败:%d\n"), GetLastError());
            return -1;
        }
    }

    //等待所有线程结束
    WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

    //关闭所有子线程
    for (int i = 0; i < THREADCOUNT;i++){
        CloseHandle(aThread[i]);
    }

    //关闭信号量
    CloseHandle(g_hSemaphore);

    _tsystem(_T("PAUSE"));
    return 0;
}

//线程函数
DWORD  WINAPI ThreadProc(LPVOID pvParam)
{
    DWORD  dwWaitResult;
    BOOL bContinue = TRUE;
    LONG lPrevCount=0;

    while (bContinue){
        //等待资源,立即返回
        dwWaitResult = WaitForSingleObject(g_hSemaphore, 0L);
        switch (dwWaitResult)
        {
        case WAIT_OBJECT_0:
            bContinue = FALSE;
            _tprintf(_T("线程%d:等待成功!\n"), GetCurrentThreadId());

            Sleep(20);

            if (!ReleaseSemaphore(g_hSemaphore, 1, &lPrevCount)){
                _tprintf(_T("ReleaseSemaphore失败:%d!\n"), GetLastError());
            }    

            //_tprintf(_T("线程%d:等待成功,当前可用资源%d!\n"), GetCurrentThreadId(),lPrevCount+1);

            break;
        case WAIT_TIMEOUT:
            _tprintf(_T("线程%d:等待超时!\n"), GetCurrentThreadId());
            break;
        }
    }
    _tprintf(_T("线程%d:退出\n"), GetCurrentThreadId());
    return 0;
}

9.6 互斥量内核对象(Mutex)

(1)互斥量的组成

  ①使用计数:与其他内核对象,都有一个使用计数

  ②线程ID:标识当前占用这个互斥量是哪个线程

  ③递归计数:该线程占用互斥量的次数

(2)互斥量的规则

  ①如果线程ID为0(即无效ID),那么该互斥量不为任何线程所占,即处于触发状态

  ②如果线程ID为非零值,表示该线程己经占用该互斥量,它处于非触发状态。

  ③其他内核对象只会记录哪些线程正在等待,但互斥量还会记录哪个线程等待成功。这使得它在未被触发的时候,也可以被线程所获得。当任何线程试图调用ReleasMutex时,该函数会检查调用线程的ID与互斥量内部保存的ID是否一致。如果一致,则递归计数递减。否则返回FALSE,这时调用GetLastError将得到ERROR_NOT_OWNER。

(3)互斥量的使用方法

  ①创建互斥量:CreateMutext(psa,bInitialOwner,pszName);其中bInitialOwner为TRUE时表示将调用线程ID设为互斥量内部的线程ID,递归计数加1。否则表示互斥量不被占用(处于触发状态)

  ②释放互斥量:ReleaseMutex,递归计数减1,当递归计数为0时,将线程ID设为0。这里互斥量被触发。

9.6.1 互斥量被“遗弃问题”

如果占用互斥量的线程在释放互斥量之前提前终止,这种现象叫互斥量被“遗弃”(Abandoned)系统会自动将该互斥量的线程ID和递归计数设为0。再“公平地”唤醒正在等待的一个线程,但这时唤醒的线程从Wait*中返回的不再是WAIT_OBJECT_0,而是返回WAIT_ABANDONED,以表示该线程等到了一个被遗弃的互斥量。所以此时被互斥量保护的资源处于什么状态,这个被唤醒的线程并不知道,要求应用程序自己决定怎么做。

9.6.2 互斥量与关键段的比较


特征


互斥量


关键段


性能




是否能跨进程使用




声时


HANDLE hmtx


CRITICAL_SECTION cs;


初始化


CreateMutex


InitializeCriticalSection


清理


CloseHandle


DeleteCriticalSection


无限等待


Wait*(hmtx,INFINITE)


EnterCriticalSection(&cs)


0等待


Wait*(hmtx,0)


TryEnterCriticalSection


任意时长等待


Wait*(htmx,dwMilliseconds)


不支持


释放


ReleaseMutex


LeaveCriticalSection


是否能同时等待其他内核对象


如WaitForMultipleObject


时间: 2024-10-09 15:15:23

第9章 用内核对象进行线程同步(3)_信号量(semaphore)、互斥量(mutex)的相关文章

第9章 用内核对象进行线程同步(2)_可等待计时器(WaitableTimer)

9.4 可等待的计时器内核对象——某个指定的时间或每隔一段时间触发一次 (1)创建可等待计时器:CreateWaitableTimer(使用时应把常量_WIN32_WINNT定义为0x0400) 参数 描述 psa 安全属性(如使用计数.句柄继承等) bManualReset 手动重置计时器还是自动重置计时器. ①当手动计时器被触发,所有正在等待计时器的线程都变可为可调度. ②当自动计时器被触发时,只有一个正在等待计数器的线程变为可调度 pszName 对象的名字 (2)也可以打开一个己经存在的

第9章 用内核对象进行线程同步(4)_死锁(DeadLock)及其他

9.7 线程同步对象速查表 对象 何时处于未触发状态 何时处于触发状态 成功等待的副作用 进程 进程仍在运行的时候 进程终止的时(ExitProcess.TerminateProcess) 没有 线程 线程仍在运行的时候 线程终止的时候(ExitThread.TermimateThread) 没有 作业 作业尚未超时的时候 作业超时的时候 没有 文件 有待处理的I/O请求的时候 I/O请求完成的时候 没有 控制台输入 没有输入的时候 有输入的时候 没有 文件变更通知 文件没有变更的时候 文件系统

线程同步——内核对象实现线程同步——信号量

1 /* 2 3 信号量内核对象 4 信号量与其它内核量相同,包含一个使用计数,除此之外还包含两个量. 5 一个最大资源计数和一个当前资源计数. 6 信号量规则如下: 7 如果当前资源计数大于0,那么信号量处于触发状态. 8 如果当前资源计数等于0,那么信号量处于未触发状态. 9 系统绝不会让当前资源计数变为负数. 10 当前资源计数绝不会大于最大资源计数. 11 12 下面我们看一下信号量的创建函数 13 HANDLE CreateSemaphore( 14 LPSECURITY_ATTRIB

线程同步——内核对象实现线程同步——事件内核对象

1 事件内核对象 2 3 事件类型对象有两种不同类型,手动重置和自动重置 4 手动重置:当一个手动重置对象被触发时候,等待该对象的所有线程变为可调度. 5 自动重置:当一个自动重置对象被触发时,只有一个等待该事件的线程会变为可调度 6 7 下面是一个创建事件内核对象的函数: 8 HANDLE CreateEvent( 9 LPSECURITY_ATTRIBUTES lpEventAttributes, 10 BOOL bManualReset, 11 BOOL bInitialState, 12

线程同步——内核对象实现线程同步——可等待计时器内核对象

1 可等待计时器 2 可等待计时器是这样一种内核对象,他们会在某个指定的时间触发或每隔一段时间触发一次. 5 下面我们来介绍一下创建可等待计时器函数: 6 7 HANDLE CreateWaitableTimer( 8 LPSECURITY_ATTRIBUTES lpTimerAttributes, 9 BOOL bManualReset, 10 LPCSTR lpTimerName ); 11 第一.三个参数我想我也不用介绍了哈,这是创建内核对象基本都会有的参数. 12 第二个参数bManua

线程同步——内核对象实现线程同步——等待函数

1 对于内核对象实现线程同步,不得不提三点: 2 1)大多数内核对象既有触发也有未触发两个状态 3 比如:进程.线程.作业.文件流.事件.可等待的计时器.信号量.互斥量 4 2)等待函数:等待函数使线程自愿进入等待状态,直到指定的内核对象变为触发状态为止, 5 说道等待我们最喜欢不过了,因为这样不会浪费我们宝贵的CPU时间. 6 3)对于自动重置对象来说,当对象被触发时,函数会自动检测到(手动重置对象为触发是,函数也能检测到), 7 并开始执行,但是在函数会在返回之前使事件变为非触发状态. 8

内核对象进行线程同步

前言: 具体的可等待的内核对象有: 进程,线程,作业,文件以及控制台的标准输入流/输出流/错误流,事件,可等待的计时器,信号量,互斥量. 等待函数: DWORD WaitForSingleObject( HANDLE hObject,//用来标识要等待的内核对象 DWORD dwMilliseconds);//等待的时间 DWORD WaitForMultipleObjects( DWORD dwCount,//函数检查的内核对象的数量(最大为MAXIMUM_WAIT_OBJECTS) CONS

秒杀多线程第九篇 经典线程同步总结 关键段 事件 互斥量 信号量

版权声明:本文为博主原创文章,未经博主允许不得转载. 前面<秒杀多线程第四篇一个经典的多线程同步问题>提出了一个经典的多线程同步互斥问题,这个问题包括了主线程与子线程的同步,子线程间的互斥,是一道非常经典的多线程同步互斥问题范例,后面分别用了四篇 <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> <秒杀多线程第七篇经典线程同步互斥量Mutex> <秒杀多线程第八篇经典线程同步信号量Semaphore> 来

经典线程同步总结 关键段 事件 互斥量 信号量

前面<秒杀多线程第四篇一个经典的多线程同步问题>提出了一个经典的多线程同步互斥问题,这个问题包括了主线程与子线程的同步,子线程间的互斥,是一道非常经典的多线程同步互斥问题范例,后面分别用了四篇 <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> <秒杀多线程第七篇经典线程同步互斥量Mutex> <秒杀多线程第八篇经典线程同步信号量Semaphore> 来详细介绍常用的线程同步互斥机制--关键段.事件.互斥量