【多线程】C++ 互斥锁(mutex)的简单原理分析

多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下,分为两种类型的多任务处理:基于进程和基于线程

  1)基于进程的多任务处理是程序的并发执行。

  2)基于线程的多任务处理是同一程序的片段的并发执行。

多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程,每个线程定义了一个单独的执行路径。在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。比如说,同一个文件,可能一个线程会对其进行写操作,而另一个线程需要对这个文件进行读操作,可想而知,如果写线程还没有写结束,而此时读线程开始了,或者读线程还没有读结束而写线程开始了,那么最终的结果显然会是混乱的。为了保护共享资源,在线程里也有这么一把锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。

一、创建线程

在Windows下用C++创建线程需要导入windows.h头文件,同时调用CreateThread()函数。如下:

#include <windows.h>
HANDLE thread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);

参数说明:

  1)In_opt LPSECURITY_ATTRIBUTES:lpThreadAttributes, {安全设置}

  指向 TSecurityAttributes 结构的指针,一般都是置为NULL,这表示没有访问限制。

  2)In SIZE_T:dwStackSize, {堆栈大小}

  分配给线程的堆栈大小,每个线程都有自己独立的堆栈(也拥有自己的消息队列)。它们都是进程中的内存区域,主要是存取方式不同(栈:先进后出;堆:先进先出)。

  3)In LPTHREAD_START_ROUTINE:lpStartAddress, {入口函数}

  线程入口函数的参数是个无类型指针,用它可以指定任何数据。

  4)In_opt __drv_aliasesMem LPVOID:lpParameter, {函数参数}

  函数指针,新线程建立后将立即执行该函数,函数执行完毕,系统将销毁此线程从而结束多线程的程序。

  5)In DWORD:dwCreationFlags, {启动选项}

  启动选项)有两个可选值:0(线程建立后立即执行入口函数);CREATE_SUSPENDED(线程建立后会挂起等待)。

  6)Out_opt LPDWORD:lpThreadId {输出线程id}

  输出线程句柄ID,注意: 1、线程的 ID 是唯一的;而句柄可能不只一个,比如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 复制一个句柄等。2、ID 比句柄更轻便,在主线程中 GetCurrentThreadId、MainThreadID获取的都是主线程的 ID。

返回值:线程句柄 ,"句柄" 类似指针,但通过指针可读写对象,通过句柄只是使用对象;有句柄的对象一般都是系统级别的对象(或叫内核对象)。

二、创建互斥量

互斥量和二元信号量类似,资源仅允许一个线程访问。与二元信号量不同的是,信号量在整个系统中可以被任意线程获取和释放,也就是说,同一个信号量可以由一个线程获取而由另一线程释放。而互斥量则要求哪个线程获取了该互斥量锁就由哪个线程释放,其它线程越俎代庖释放互斥量是无效的。

在使用互斥量进行线程同步时会用到以下几个函数:

HANDLE WINAPI CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,        //线程安全相关的属性,常置为NULL
    BOOL                  bInitialOwner,            //创建Mutex时的当前线程是否拥有Mutex的所有权
    LPCTSTR               lpName                    //Mutex的名称
);

其中,lpMutexAttributes也是表示安全的结构,与CreateThread中的lpThreadAttributes功能相同,表示决定返回的句柄是否可被子进程继承,如果为NULL则表示返回的句柄不能被子进程继承。bInitialOwner表示创建Mutex时的当前线程是否拥有Mutex的所有权,若为TRUE则指定为当前的创建线程为Mutex对象的所有者,其它线程访问需要先ReleaseMutex。lpName为Mutex的名称。

DWORD WINAPI WaitForSingleObject(
    HANDLE hHandle,                                 //要获取的锁的句柄
    DWORD  dwMilliseconds                           //超时间隔
);

其中,WaitForSingleObject的作用是等待一个指定的对象(如Mutex对象),直到该对象处于非占用的状态(如Mutex对象被释放)或超出设定的时间间隔。除此之外,还有一个与它类似的函数WaitForMultipleObjects,它的作用是等待一个或所有指定的对象,直到所有的对象处于非占用的状态,或超出设定的时间间隔。

BOOL WINAPI ReleaseMutex(HANDLE hMutex);

Handle:要等待的指定对象的句柄。dwMilliseconds:超时的间隔,以毫秒为单位;如果dwMilliseconds为非0,则等待直到dwMilliseconds时间间隔用完或对象变为非占用的状态,如果dwMilliseconds 为INFINITE则表示无限等待,直到等待的对象处于非占用的状态。释放所拥有的互斥量锁对象,hMutex为释放的互斥量的句柄。

int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr , int *restrict type);   //获取互斥锁类型
int pthread_mutexattr_settype(const pthread_mutexattr_t *restrict attr , int type);             //设置互斥锁类型

参数type表示互斥锁的类型,总共有以下四种类型:
  1)PTHREAD_MUTEX_NOMAL:标准互斥锁,第一次上锁成功,第二次上锁会失败并阻塞;
  2)PTHREAD_MUTEX_RECURSIVE:递归互斥锁,第一次上锁成功,第二次上锁还是会成功,可以理解为内部有一个计数器,每加一次锁计数器加1,解锁减1;
  3)PTHREAD_MUTEX_ERRORCHECK:检查互斥锁,第一次上锁会成功,第二次上锁出错返回错误信息,不会阻塞;
  4)PTHREAD_MUTEX_DEFAULT:默认互斥锁,第一次上锁会成功,第二次上锁会失败。

三、采用互斥锁实现线程同步

#include <windows.h>
#include <iostream>
 
#define NAME_LINE   40
 
//定义线程函数传入参数的结构体
typedef struct __THREAD_DATA
{
    int nMaxNum;
    char strThreadName[NAME_LINE];
 
    __THREAD_DATA() : nMaxNum(0)
    {
        memset(strThreadName, 0, NAME_LINE * sizeof(char));
    }
}THREAD_DATA;
 
HANDLE g_hMutex = NULL;     //互斥量
 
//线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    THREAD_DATA* pThreadData = (THREAD_DATA*)lpParameter;
 
    for (int i = 0; i < pThreadData->nMaxNum; ++ i)
    {
        //请求获得一个互斥量锁
        WaitForSingleObject(g_hMutex, INFINITE);
        cout << pThreadData->strThreadName << " --- " << i << endl;
        Sleep(100);
        //释放互斥量锁
        ReleaseMutex(g_hMutex);
    }
    return 0L;
}
 
int main()
{
    //创建一个互斥量
    g_hMutex = CreateMutex(NULL, FALSE, NULL);
 
    //初始化线程数据
    THREAD_DATA threadData1, threadData2;
    threadData1.nMaxNum = 5;
    strcpy_s(threadData1.strThreadName, "线程1");
    threadData2.nMaxNum = 10;
    strcpy_s(threadData2.strThreadName, "线程2");
 
 
    //创建第一个子线程
    HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc, &threadData1, 0, NULL);
    //创建第二个子线程
    HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc, &threadData2, 0, NULL);
    //关闭线程
    CloseHandle(hThread1);
    CloseHandle(hThread2);
 
    //主线程的执行路径
    for (int i = 0; i < 5; ++ i)
    {
        //请求获得一个互斥量锁
        WaitForSingleObject(g_hMutex, INFINITE);
        cout << "主线程 === " << i << endl;
        Sleep(100);
        //释放互斥量锁
        ReleaseMutex(g_hMutex);
    }
 
    system("pause");
    return 0;
}

posted @ 2021-07-13 21:24  Jcpeng_std  阅读(2974)  评论(0编辑  收藏  举报