Semaphore

一、信号量

1.说明

信号量是一个同步对象,用于维护零和指定最大值之间的计数。 每次线程完成信号灯对象的等待时,计数都会递减,每次线程释放信号灯时递增。 

当计数达到零时,不会再成功等待信号灯对象状态发出信号。 当信号量计数大于零时,会将信号量的状态设置为已发出信号;当信号量计数为零时,会将信号量的状态设置为未发出信号。

信号灯的初始计数通常设置为最大值。 然后,随着使用受保护资源,计数将从该级别递减。 或者,可以在初始化应用程序时创建一个信号灯,其初始计数为零,以阻止对受保护资源的访问。 初始化后,可以使用 ReleaseSemaphore 将计数递增为最大值。

2.例子

应用程序可能会对所创建的窗口数施加限制。 它使用等于窗口限制的最大计数的信号灯,每当创建窗口时递减计数,并在关闭窗口时递增计数。 应用程序指定在创建每个窗口之前调用其中一个 等待函数 的信号灯对象。 当计数为零(指示已达到窗口限制)时,等待函数会阻止执行窗口创建代码。

3.代码样例

#include <windows.h>
#include <stdio.h>

#define MAX_SEM_COUNT 10
#define THREADCOUNT 12

HANDLE ghSemaphore;

DWORD WINAPI ThreadProc(LPVOID);

int main(void)
{
    HANDLE aThread[THREADCOUNT];
    DWORD ThreadID;
    int i;

    // Create a semaphore with initial and max counts of MAX_SEM_COUNT

    ghSemaphore = CreateSemaphore(
        NULL,          // default security attributes
        MAX_SEM_COUNT, // initial count
        MAX_SEM_COUNT, // maximum count
        NULL);         // unnamed semaphore

    if (ghSemaphore == NULL)
    {
        printf("CreateSemaphore error: %d\n", GetLastError());
        return 1;
    }

    // Create worker threads

    for (i = 0; i < THREADCOUNT; i++)
    {
        aThread[i] = CreateThread(
            NULL, // default security attributes
            0,    // default stack size
            (LPTHREAD_START_ROUTINE)ThreadProc,
            NULL,       // no thread function arguments
            0,          // default creation flags
            &ThreadID); // receive thread identifier

        if (aThread[i] == NULL)
        {
            printf("CreateThread error: %d\n", GetLastError());
            return 1;
        }
    }

    // Wait for all threads to terminate

    WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

    // Close thread and semaphore handles

    for (i = 0; i < THREADCOUNT; i++)
        CloseHandle(aThread[i]);

    CloseHandle(ghSemaphore);

    system("pause");

    return 0;
}

DWORD WINAPI ThreadProc(LPVOID lpParam)
{

    // lpParam not used in this example
    UNREFERENCED_PARAMETER(lpParam);

    DWORD dwWaitResult;
    BOOL bContinue = TRUE;

    while (bContinue)
    {
        // Try to enter the semaphore gate.

        dwWaitResult = WaitForSingleObject(
            ghSemaphore, // handle to semaphore
            0L);         // zero-second time-out interval

        switch (dwWaitResult)
        {
        // The semaphore object was signaled.
        case WAIT_OBJECT_0:
            // TODO: Perform task
            printf("Thread %d: wait succeeded\n", GetCurrentThreadId());
            bContinue = FALSE;

            // Simulate thread spending time on task
            Sleep(5);

            // Release the semaphore when task is finished

            if (!ReleaseSemaphore(
                    ghSemaphore, // handle to semaphore
                    1,           // increase count by one
                    NULL))       // not interested in previous count
            {
                printf("ReleaseSemaphore error: %d\n", GetLastError());
            }
            break;

        // The semaphore was nonsignaled, so a time-out occurred.
        case WAIT_TIMEOUT:
            printf("Thread %d: wait timed out\n", GetCurrentThreadId());
            break;
        }
    }
    return TRUE;
}

输出:

#define MAX_SEM_COUNT 10
#define THREADCOUNT 12

12个线程,最大信号量为10,同时呢:

        dwWaitResult = WaitForSingleObject(
            ghSemaphore, // handle to semaphore
            0L);         // zero-second time-out interval零秒超时间隔

所以会有2个线程超时

二、有关函数

(〇)HANDLE

句柄是一个用来标识对象或者项目的标识符,可以用来描述窗体、文件等,还有注意句柄(Handle)不能是常量

从数据类型上来看它只是一个32位(或者64位)的无符号整数

句柄与普通地址的区别在于,指针包含的是引用对象的内存地址,而句柄则是由系统所管理的引用标识,该标识可以被系统重新定位到一个新的内存地址上

HANDLE m_hSemaphore;

(一)创建信号量CreateSemaphore

1.说明

CreateSemaphore 是创建信号量

2.函数原型

#define CreateSemaphore __MINGW_NAME_AW(CreateSemaphore)
  WINBASEAPI HANDLE WINAPI CreateSemaphoreW (
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, 
LONG lInitialCount, 
LONG lMaximumCount, 
LPCWSTR lpName);

lpSemaphoreAttributes 是信号量的安全属性。

指向SECURITY_ATTRIBUTES结构的指针。如果此参数为NULL,则子进程无法继承句柄。

lInitialCount 是初始化的信号量。

信号量对象的初始计数。该值必须大于或等于零且小于或等于lMaximumCount。信号量的状态在其计数大于零时发出信号,在信号为零时发出信号。只要wait函数释放等待信号量的线程,计数就会减1。通过调用ReleaseSemaphore函数将计数增加指定的量。

lMaximumCount 是允许信号量增加到最大值。

信号量对象的最大计数。该值必须大于零。

lpName 是信号量的名称。

信号量对象的名称。名称仅限于MAX_PATH个字符。名称比较区分大小写。

如果lpName为NULL,则创建没有名称的信号量对象。

返回值

如果函数成功,则返回值是信号量对象的句柄。如果在函数调用之前存在命名的信号量对象,则该函数返回现有对象的句柄,GetLastError返回ERROR_ALREADY_EXISTS。

如果函数失败,则返回值为NULL。要获取扩展错误信息,请调用GetLastError。

3.代码示例

    // 创建对应的信号量
    m_Semaphore.Create(0, SysInfo.dwNumberOfProcessors);
m_hSemaphore = ::CreateSemaphore(NULL, lInitialCount, lMaximumCount, NULL);
    
if (m_hSemaphore == NULL) // 如果函数失败,则返回值为NULL。
{
       return false;
}

4.SYSTEM_INFO介绍

SYSTEM_INFO,Win32 API函数GetSystemInfo所使用的结构体。

SYSTEM_INFO结构体包含了当前计算机的信息。这个信息包括计算机的体系结构、中央处理器的类型、系统中中央处理器的数量、页面的大小以及其他信息。

5.SYSTEM_INFO结构原型

  typedef struct _SYSTEM_INFO {
    __C89_NAMELESS union {
      DWORD dwOemId;    //过时已废弃
      __C89_NAMELESS struct {
    WORD wProcessorArchitecture;    //处理器架构
    WORD wReserved;
      } DUMMYSTRUCTNAME;
    } DUMMYUNIONNAME;
    DWORD dwPageSize;   //指定页面的大小和页面保护和委托的颗粒。
    LPVOID lpMinimumApplicationAddress;    //应用程序最小地址
    LPVOID lpMaximumApplicationAddress;    //应用程序最大地址
    DWORD_PTR dwActiveProcessorMask;    //处理器掩码
    DWORD dwNumberOfProcessors;    //处理器数量
    DWORD dwProcessorType;    //处理器类型
    DWORD dwAllocationGranularity;    //虚拟内存分配粒度
    WORD wProcessorLevel;    //处理器级别
    WORD wProcessorRevision;    //处理器版本
  } SYSTEM_INFO, *LPSYSTEM_INFO;

 

6.SYSTEM_INFO使用样例

在CreateSemaphore前使用,获得系统处理器数量

void CTestDlg::GetSysInfo()
{
 SYSTEM_INFO  sysInfo;
 GetSystemInfo(&sysInfo);
}

GetSystemInfo函数用于获取当前系统的信息

(二)等待信号WaitForSingleObject

1.说明

信号量的作用简单理解就是一个标志位,假设某个文件就有一个信号量,初始时我们设信号量为FALSE,而只有当信号量为FALSE时线程才可以打开访问这个文件。那么,当第一个线程到达,信号量为FALSE,线程打开文件进行访问,并将信号量置为TRUE;在第一个线程在访问文件时,第二个线程到来,此时信号量仍未TRUE,所以第二个线程等待,这个等待的过程就是WaitForSingleObject。

WaitForSingleObject在等待的过程中会进入一个非常高效的沉睡等待状态,只占用极少的CPU时间片。

WaitForSingleObject 将信号量的计数递减一

2.函数原型

function WaitForSingleObject(
  hHandle: THandle;     // {要等待的对象句柄}
  dwMilliseconds: DWORD  //{等待的时间, 单位是毫秒}WaitForSingleObject 的第二个参数一般给常数值 INFINITE, 表示一直等下去, 死等.
): DWORD; stdcall;      // {返回值如下:}
WAIT_OBJECT_0  //{等着了, 本例中是: 等的那个进程终于结束了}
WAIT_TIMEOUT   //{等过了点(你指定的时间), 也没等着}
WAIT_ABANDONED //{好不容易等着了, 但人家还是不让咱执行; 这一般是互斥对象}

3.代码示例

// 等待任务来临
pThis->m_Semaphore.WiteForSemaphore();
return ::WaitForSingleObject(m_hSemaphore, INFINITE);

4.WaitForSingleObject程序示例

#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <string.h>
#include <vector>
#include <WinSock2.h>
#include <Winsock2.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")

using namespace std;

int i = 0;

DWORD WINAPI FunProc(LPVOID lpParameter)
{
    for (; i < 10; i++) // 这里把10改成100
    {
        cout << i << endl;
    }
    return 0;
}

int main()
{
    cout << i << endl;
    HANDLE hThread;
    hThread = CreateThread(NULL, 0, FunProc, NULL, 0, NULL);
    DWORD dwRet = WaitForSingleObject(hThread, 1);
    if (dwRet == WAIT_OBJECT_0)
    {
        cout << "创建的线程执行结束" << endl;
    }
    if (dwRet == WAIT_TIMEOUT)
    {
        cout << "等待超时" << endl;
    }
    if (dwRet == WAIT_ABANDONED)
    {
        cout << "Abandoned" << endl;
    }
    cout << "判断完毕" << endl;
    CloseHandle(hThread);
    system("pause");
    return 0;
}

循环10次的输出:

循环100次的输出:

(三)释放信号量ReleaseSemaphore

1.说明

按指定数量增加指定信号量对象的计数。

2.函数原型

WINBASEAPI WINBOOL WINAPI ReleaseSemaphore (
HANDLE hSemaphore,     //要增加的信号量句柄。
LONG lReleaseCount,     //增加的计数。
//如果指定的计数将导致信号量的计数超过在创建信号时指定的最大计数, 则不会更改计数, 并且函数返回FALSE.
LPLONG lpPreviousCount    //指向一个变量的指针,用于接收信号灯的上一个计数。 
            //如果不需要上一个计数,此参数可以为 NULL 。
);
//返回值
//如果该函数成功,则返回值为非零值。
//如果函数失败,则返回值为零。 要获得更多的错误信息,请调用 GetLastError。

3.代码示例

    BOOL bRet = ::ReleaseSemaphore(m_hSemaphore, lReleaseCount, NULL);
    if (bRet == 0)
    {
        return false;
    }
    return true;

(四)关闭句柄CloseHandle

1.说明

关闭打开的对象句柄。

由于信号量是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。

2.函数原型

BOOL CloseHandle(
  [in] HANDLE hObject//打开对象的有效句柄。
);
//如果该函数成功,则返回值为非零值。
//如果函数失败,则返回值为零。

3.代码示例

    if (m_hSemaphore != NULL)
    {
        ::CloseHandle(m_hSemaphore);
        m_hSemaphore = NULL;
    }

 

posted @ 2023-02-26 13:15  ImreW  阅读(27)  评论(0编辑  收藏  举报