多线程基础之二:mutex和semaphore使用方法

news/2024/5/19 7:16:50 标签: 多线程, mutex, semaphore

mutexsemaphore都是内核对象,是用来实现多进程间或多线程锁机制的基础。本文将要介绍两者的使用方式。


0. 多线程锁机制涉及的Windows API

创建mutex内核对象,用来作为线程间或进程间通信的API接口如下

HANDLE  WINAPI CreateMutex( __in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, __in BOOL bInitialOwner, __in_opt LPCTSTR lpName);

//lpMutexAttributes:第一个参数表示安全控制,一般直接传入NULL
//bInitialOwner:第二个参数用来确定互斥量的初始拥有者。如果传入TRUE表示互斥量对象内部会记录创建它的线程ID,并将递归计数置为1,由于该线程ID非零,所以互斥量处于未触/无信号/未通知状态,表示互斥量为创建线程拥有;
如果传入False,那么互斥量对象内部的线程ID号设置为NULL,递归计数设置为0,这意味着互斥量不为任何线程占用,处于触发/有信号/已通知状态。
//lpName:第三个参数用来设置互斥量的名称,多线程就是通过名称来确保它们访问的是同一个互斥量。
(Mutex不仅可以多线程使用,也可以跨进程使用,所以其名称对于整个系统而言是全局的,故而起名字时尽量复杂)。

既然是关于多线程的锁使用问题,那么显然需要介绍下创建线程的API,函数原型:

HANDLE  WINAPI  CreateThread(
  LPSECURITY_ATTRIBUTES    lpThreadAttributes,
  SIZE_T     dwStackSize,
  LPTHREAD_START_ROUTINE  lpStartAddress,
  LPVOID    lpParameter,
  DWORD    dwCreationFlags,
  LPDWORD  lpThreadId
);
函数说明:
第一个参数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。
第二个参数表示线程栈空间大小。传入0表示使用默认大小(1MB)。
第三个参数表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址,传入函数名即可。
第四个参数是传给线程函数的参数。
第五个参数指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。
第六个参数将返回线程的ID号,传入NULL表示不需要返回该线程ID号。

函数返回值:
成功返回新线程的句柄,失败返回NULL。通过该API的返回值HANDLE,可以看到操作系统将函数、线程、进程、文件、窗口等都统一看作“文件”对象,故而都可以使用Handle统一来指引到目的地址。

既然是使用锁机制,那么显然需要介绍锁的获取方式,即acquire(lock)的应该采用的API,其中最为重要的莫过于WaitForSingleObject和WaitForMultipleObjects两个API。

WaitForSingleObject
函数功能:等待函数 – 使线程进入等待状态,直到指定的内核对象被触发。
函数原形:
DWORD  WINAPI  WaitForSingleObject(
  HANDLEhHandle,
  DWORDdwMilliseconds
);
函数说明:
第一个参数为要等待的内核对象,该内核对象只需支持信号通知即可,如event, mutex, process, thread, semaphore。
第二个参数为最长等待的时间,以毫秒为单位,如传入5000就表示5秒,传入0就立即返回,传入INFINITE表示无限等待。
WaitForMultipleObjects
是Windows中的一个功能非常强大的函数,几乎可以等待Windows中的所有的内核对象
函数原型为:
DWORD WaitForMultipleObjects(  
  DWORD nCount,             // number of handles in the handle array  
  CONST HANDLE *lpHandles,  // pointer to the object-handle array  
  BOOL fWaitAll,            // wait flag  
  DWORD dwMilliseconds      // time-out interval in milliseconds  
);  
参数解析:
DWORD 就是 Double Word, 每个word为2个字节的长度,DWORD双字即为4个字节,每个字节是8位。
nCount  指定列表中的句柄数量,最大值为MAXIMUM_WAIT_OBJECTS(64)  
*lpHandles 句柄数组的指针。lpHandles为指定对象句柄组合中的第一个元素 HANDLE类型可以为(Event,Mutex,Process,Thread,Semaphore)数组  
bWaitAll 等待的类型,如果为TRUE,表示除非对象都发出信号,否则就一直等待下去;如果FALSE,表示任何对象发出信号即可 
dwMilliseconds指定要等候的毫秒数。如设为零,表示立即返回。如指定常数INFINITE,则可根据实际情况无限等待下去。

关于信号量semaphore的介绍较为复杂,是因为信号量使用的场景更为复杂。首先介绍创建信号量的API接口函数

HANDLE WINAPI CreateSemaphore( 
  _In_opt_  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes 
  _In_      LONG lInitialCount, 
  _In_      LONG lMaximumCount, 
  _In_opt_  LPCTSTR lpName 
);
第一个参数:安全属性,如果为NULL则是默认安全属性 
第二个参数:信号量的初始值,要>=0<=第三个参数 
第三个参数:信号量的最大值,即最大资源数目 
第四个参数:信号量的名称,一般不涉及到跨进程使用基本都是输入NULL的。 
返回值:指向信号量的句柄,如果创建的信号量和已有的信号量重名,那么返回已经存在的信号量句柄
ReleaseSemaphore
函数功能:为指定的信号量增加指定的资源计数
HANDLE  ReleaseSemaphore (
HANDLE  hSemaphore,
LONG     lReleaseCount,
PLONG    plPreviousCount
);
第一个参数:目标信号量句柄
第二个参数:本次释放指定的增加资源计数
第三个参数:返回当前资源计数的原始值,若设置NULL,则不返回。

信号量的使用方法或流程如下:
1、创建一个信号量:CreateSemaphore;
2、使用信号量名字,打开一个已经存在的信号量:OpenSemaphore;
3、监听并获得信号量的一个占有权:WaitForSingleObject、WaitForMultipleObjects 等一类等待的函数……(可能造成阻塞);
4、释放信号量的占有权,增加信号量的资源计数:ReleaseSemaphore;
5、关闭信号量,将其从内核中删除:CloseHandle;

信号量semaphore的使用规则:
1. 如果当前资源计数大于0,即信号量的可用资源数大于0,那么信号量处于触发状态,可响应任何监听获取请求;
2. 如果当前资源计数等于0,那么信号量处于未触发状态;那么系统会让调用线程进入等待状态。
CreateSemaphore(NULL,0,1,NULL); 当第二个参数为0时,表示当前信号量的可用资源数目为0,表示创建进程在创建信号量之时并占用了该信号量,其余监听信号量的线程就会进入等待状态,直到创建信号量的宿主线程释放信号量。
3. 当前资源计数绝对不会大于最大资源计数。


1. Mutex的多线程锁机制使用案例

#include <iostream>
#include <windows.h>
#include <time.h>
using namespace std;

HANDLE  g_hMutex = NULL;
const int g_Number = 3; //代表线程对象的数目
DWORD WINAPI ThreadProc1(__in  LPVOID lpParameter);
DWORD WINAPI ThreadProc2(__in  LPVOID lpParameter);
DWORD WINAPI ThreadProc3(__in  LPVOID lpParameter);

int main()
{
    clock_t start_time = clock();
    //g_hMutex = CreateMutex(NULL,FALSE,NULL);
    //TRUE代表主线程拥有互斥对象 但是主线程没有释放该对象  互斥对象谁拥有 谁释放 
    g_hMutex = CreateMutex(NULL,TRUE,NULL);
    printf("主线程创建时便占有了互斥对象,没有释放,所以其他子线程无法使用。\n");
    ReleaseMutex(g_hMutex);
    printf("主线程释放了互斥对象,其他子线程可以开始使用。\n");
    // FLASE代表当前没有线程拥有这个互斥对象

    HANDLE hThread[ g_Number ] = {0};
    int first = 1, second = 2, third = 3;
    hThread[ 0 ] = CreateThread(NULL,0,ThreadProc1,(LPVOID)first,0,NULL);
    hThread[ 1 ] = CreateThread(NULL,0,ThreadProc2,(LPVOID)second,0,NULL);
    hThread[ 2 ] = CreateThread(NULL,0,ThreadProc3,(LPVOID)third,0,NULL);

    WaitForMultipleObjects(g_Number,hThread,TRUE,INFINITE); //INFINITE代表一直等下去,该位单位为ms
    CloseHandle( hThread[0] );
    CloseHandle( hThread[1] );
    CloseHandle( hThread[2] );

    CloseHandle( g_hMutex );
    clock_t end_time = clock();
    cout<<"Running time is:"<<static_cast<double>(end_time - start_time)/CLOCKS_PER_SEC*1000<<"ms"<<endl;
    return 0;
}

DWORD WINAPI ThreadProc1(__in  LPVOID lpParameter)
{
    WaitForSingleObject(g_hMutex, INFINITE);//等待互斥量
    cout<<(int)lpParameter<<endl;
    ReleaseMutex(g_hMutex);//释放互斥量
    return 0;
}

DWORD WINAPI ThreadProc2(__in  LPVOID lpParameter)
{
    WaitForSingleObject(g_hMutex, INFINITE);//等待互斥量
    cout<<(int )lpParameter<<endl;
    ReleaseMutex(g_hMutex);//释放互斥量
    return 0;
}

DWORD WINAPI ThreadProc3(__in  LPVOID lpParameter)
{
    WaitForSingleObject( g_hMutex, INFINITE);//等待互斥量
    cout<<(int)lpParameter<<endl;
    ReleaseMutex(g_hMutex);//释放互斥量
    return 0;
}



2. semaphore多线程锁机制使用案例

#include <iostream> 
#include <windows.h>
#include <time.h> 
using namespace std;

const int g_Number = 3; 
DWORD WINAPI ThreadProc1(__in  LPVOID lpParameter); 
DWORD WINAPI ThreadProc2(__in  LPVOID lpParameter); 
DWORD WINAPI ThreadProc3(__in  LPVOID lpParameter);

HANDLE hSemp1,hSemp2,hSemp3;

int main() 
{ 
   clock_t start_time = clock();
   hSemp1 = CreateSemaphore(NULL,0,3,NULL);
   printf("信号量初始便被主线程占用,需要主线程释放后,其余线程才能使用\n");
   char ch = getchar();
   ReleaseSemaphore(hSemp1,1,NULL);
   printf("信号量可能使得多个进程并行,交替执行。\n");
   //hSemp2 = CreateSemaphore( NULL,1,1,NULL); 
   //hSemp3 = CreateSemaphore(NULL,1,1,NULL);

    HANDLE hThread[ g_Number ] = {0}; 
    int first = 1, second = 2, third = 3; 
    hThread[ 0 ] = CreateThread(NULL,0,ThreadProc1,(LPVOID)first,0,NULL); 
    hThread[ 1 ] = CreateThread(NULL,0,ThreadProc2,(LPVOID)second,0,NULL); 
    hThread[ 2 ] = CreateThread(NULL,0,ThreadProc3,(LPVOID)third,0,NULL);

    WaitForMultipleObjects(g_Number,hThread,TRUE,INFINITE); 
    CloseHandle( hThread[0] ); 
    CloseHandle( hThread[1] ); 
    CloseHandle( hThread[2] );

    CloseHandle( hSemp1 ); 
    CloseHandle( hSemp2 ); 
    CloseHandle( hSemp3 ); 

    clock_t end_time = clock();
    cout<<"Running time is:"<<static_cast<double>(end_time - start_time)/CLOCKS_PER_SEC*1000 <<"ms"<<endl;
    return 0; 
}

DWORD WINAPI ThreadProc1(__in  LPVOID lpParameter) 
{ 
    WaitForSingleObject(hSemp1, INFINITE);//等待信号量 
    cout<<(int)lpParameter<<endl; 
    ReleaseSemaphore(hSemp1,1,NULL);//释放信号量 
    return 0; 
}

DWORD WINAPI ThreadProc2(__in  LPVOID lpParameter) 
{ 
    WaitForSingleObject(hSemp1, INFINITE);//等待信号量 
    cout<<(int )lpParameter<<endl; 
    ReleaseSemaphore(hSemp1,1,NULL);//释放信号量 
    return 0; 
}

DWORD WINAPI ThreadProc3(__in  LPVOID lpParameter) 
{ 
    WaitForSingleObject( hSemp1, INFINITE);//等待信号量 
    cout<<(int)lpParameter<<endl; 
    ReleaseSemaphore(hSemp1,1,NULL);//释放信号量 
    return 0; 
}



http://www.niftyadmin.cn/n/1030877.html

相关文章

Instruments如何看Mono内存分配

1&#xff09;Instruments如何看Mono内存分配 ​2&#xff09;关于Addressable v1.11.2的疑问 3&#xff09;展开UV2时导致Mesh顶点数增加 4&#xff09;提升Unity编辑器中代码的编译速度 5&#xff09;Renderdoc调试的疑问 这是第217篇UWA技术知识分享的推送。今天我们继续为大…

多线程基础之三:使用event, mutex, semaphore实现多进程间互斥

前面文章介绍了使用mutex和semaphore在多线程场景中实现线程互斥。事实上&#xff0c;因为mutex, semaphore是内核对象&#xff0c;虽然是在某一个进程中创建的&#xff0c;但是由于进程间可以共享内核模块&#xff0c;故而使用mutex, semaphore在进程间作为互斥标识量也是可以…

多线程基础之四:Linux提供的原子锁类型atomic_t

在x86体系下&#xff0c;任何处理器平台下都会有一些原子性操作&#xff0c;在单处理器情况下&#xff0c;单步指令的原子性容易实现。但是在SMP多处理器情况下&#xff0c;只有那些单字的读&#xff08;将变量读进寄存器&#xff09;或写&#xff08;从寄存器写入到变量地址&a…

Packages目录下Shader打包疑问

1&#xff09;Packages目录下Shader打包疑问 ​2&#xff09;如何关闭资源的RW选项 3&#xff09;RenderTexture单个像素的色值大于Shader的输出值 4&#xff09;客户端背包刷新机制 5&#xff09;PBXProject.AddCapability添加失败 这是第218篇UWA技术知识分享的推送。今天我们…

多线程基础之五:Windows API提供的mutex和semaphore性能比较

Windows系统提供HANDLE WINAPI CreateSemaphore(xxx)和HANDLE WINAPI CreateMutex(xxx)直接提供Mutex和Semaphore两种内核对象供程序员使用在临界区互斥操作。在前面的多线程基础之一提到过Semaphore存在Binary和Counting两种&#xff0c;其中Binary Semaphore等同于Mutex&…

多线程基础之六:Pthread Win32实现的非阻塞请求机制的Semaphore

前面看到Windows API直接提供的Semaphore并没有为其配备等待队列&#xff0c;从而无法实现非阻塞请求机制以实现操作加速&#xff0c;对于临界区耗时的情况下&#xff0c;显然是存在实现非阻塞请求机制的Semaphore的。Linux下的Pthread库实现了这样的增加版Semaphore&#xff0…

UWA DAY 2020 课程视频已上线

“品往鉴来&#xff0c;质存高远” UWA DAY 2020已落下帷幕&#xff0c;UWA再次以丰富多样的专题内容、具有前瞻性的技术洞见呈现于众。再次感谢您对本次大会的关注与支持&#xff0c;希望对您而言这是一段美好的回忆。UWA DAY 2020的大部分议题已发布于UWA学堂&#xff0c;欢迎…

多线程基础之七:多线程遇上printf的“延迟写”策略

0. 运行库提供的IO读写函数采用“延迟写”策略的原因编程时经常会用到printf()函数&#xff0c;但是由于printf()函数涉及到和显示器或磁盘等外设进行交互&#xff0c;所以操作涉及到从“用户态–>内核态–>返回用户态”的一系列内核转换过程&#xff0c;但是从用户态通过…