C++多线程同步之Mutex(互斥量)

来源:互联网 发布:数据库认证培训 编辑:程序博客网 时间:2024/05/17 01:37

一、互斥量Mutex同步多线程

1、Win32平台

相关函数和头文件

#include <windows.h>HANDLE CreateMutex(LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针BOOLbInitialOwner, // 初始化互斥对象的所有者LPCTSTRlpName // 指向互斥对象名的指针);DWORD WINAPI WaitForSingleObject(__in HANDLE hHandle,//互斥量对象句柄__in DWORD dwMilliseconds//等待时间);BOOL WINAPI ReleaseMutex(HANDLE hMutex);返回值:BOOL,TRUE表示成功,FALSE表示失败。参数表:hMutex:HANDLE,制定一个互斥体的句柄。BOOL CloseHandle(HANDLE hObject);参数: hObject 代表一个已打开对象handle。返回值:TRUE:执行成功;FALSE:执行失败,可以调用GetLastError()获知失败原因。

源码:
从本篇开始,我对代码会进行一些封装,使之更贴近实际使用的情况。

/***MyMutex.h头文件***/#ifndef __MY_MUTEX_H#define __MY_MUTEX_H#include <windows.h>class CMyMutex{public:    CMyMutex();    virtual ~CMyMutex();    void Lock();    void UnLock();private:    HANDLE m_hMutex;};class CAutoLock{public:    CAutoLock(CMyMutex* pMutex);    virtual ~CAutoLock();private:    CMyMutex*           m_pMutex;};#endif;
/***MyMutex.cpp文件***/#include <iostream>#include <windows.h>#include "MyMutex.h"using namespace std;CMyMutex::CMyMutex(){    m_hMutex = CreateMutex(NULL               /*默认安全属性*/                        , false               /*创建线程不拥有该信号量*/                        , NULL                /*锁名称*/                        );}CMyMutex::~CMyMutex(){    if(NULL != m_hMutex)    {        CloseHandle(m_hMutex);        cout<<"m_hMutex被关闭"<<endl;    }}void CMyMutex::Lock(){    if(NULL == m_hMutex)    {        cout<<"m_hMutex为空"<<endl;        return;    }    DWORD dRes = -1;    dRes = WaitForSingleObject(m_hMutex, INFINITE);    if(WAIT_OBJECT_0  == dRes)    {//      cout<<"上锁成功!"<<endl;    }    else if(WAIT_ABANDONED == dRes)    {        cout<<"发生锁死现象"<<endl;    }    else if(WAIT_TIMEOUT == dRes)    {        cout<<"等待超时"<<endl;    }    else if(WAIT_FAILED == dRes)    {        cout<<"发生错误"<<endl;    }    else    {        cout<<"上锁失败!"<<endl;    }}void CMyMutex::UnLock(){    ReleaseMutex(m_hMutex);}//****************************CAutoLock*****************************************CAutoLock::CAutoLock(CMyMutex* pMutex){    m_pMutex = pMutex;    m_pMutex->Lock();}CAutoLock::~CAutoLock(){    m_pMutex->UnLock();}
/***main.cpp文件***/#include <iostream>#include <windows.h>#include "MySemaphore.h"#include "MyMutex.h"using namespace std;CMyMutex        MyMutex;/*声明一个全局的互斥量对象(自己封装的)*/DWORD WINAPI Fun(LPVOID lpParamter){    string strPrint((const char*)lpParamter);    int iRunTime = 0;    //执行100次跳出    while(++iRunTime<100)    {        /*利用CMyMutex的构造函数和析构函数分别取创建和关闭互斥量          利用CAutoLock的构造和析构函数去WaitForSingleObject和ReleaseMutex互斥量        */        CAutoLock cLock(&MyMutex);        cout <<"["<< iRunTime <<"]:"<< strPrint.c_str()<<endl;        //线程函数阻塞,交出CPU使用权限        Sleep(10);    }    return 0;}int main(){    //创建子线程    string str1 = "A";    string str2 = "B";    string str3 = "C";    string str4 = "D";    string str5 = "E";    HANDLE hThread1 = CreateThread(NULL, 0, Fun, (void*)str1.c_str(), 0, NULL);    HANDLE hThread2 = CreateThread(NULL, 0, Fun, (void*)str2.c_str(), 0, NULL);    HANDLE hThread3 = CreateThread(NULL, 0, Fun, (void*)str3.c_str(), 0, NULL);    HANDLE hThread4 = CreateThread(NULL, 0, Fun, (void*)str4.c_str(), 0, NULL);    HANDLE hThread5 = CreateThread(NULL, 0, Fun, (void*)str5.c_str(), 0, NULL);    //关闭线程    CloseHandle(hThread1);    CloseHandle(hThread2);    CloseHandle(hThread3);    CloseHandle(hThread4);    CloseHandle(hThread5);    getchar();//  system("pause");    return 0;}

运行结果:这里写图片描述
五个线程分别打印字符串A到E,各执行99次,没有出现打印混乱(对屏幕资源进行争夺)的情况。

另外有兴趣的读者可以把代码敲一遍,每个线程打印9次,然后把CAutoLock的析构函数内的 m_pMutex->UnLock();注释起来会出现什么情况?可以思考一下。
运行结果:这里写图片描述
出现的现象是:每个线程打印了9次就出现了“发生死锁现象”,而且打印A的线程居然可以不停的对m_pMutex->Lock();这是为什么呢?
WAIT_ABANDONED 0x00000080:当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值。这就是为什么会打印“发生死锁现象”,可能这里的提示写的不是很恰当。
另外可以重复执行m_pMutex->Lock();是因为打印A线程从最开始已经WaitForSingleObject到该互斥量,并且处于有信号状态,因此该线程可以一直打印,打印9次之后,线程已经关闭(实际上线程在打印完9次之前已经被CloseHandle()了),因此才会出现返回WAIT_ABANDONED 。
在这里为什么打印D线程又能WaitForSingleObject,使互斥量变为有信号状态,那可能就需要知道系统会对未释放核心对象互斥量进行什么处理。从执行结果看,系统又把它变为有信号状态,让其他线程可用了。

2、Linux平台

相关头文件和API

#include<pthread.h>#include<errno.h>//初始化信号量接口,如果使用默认的属性初始化互斥量, 只需把attr设为NULL.int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restric attr);//销毁信号量对象接口int pthread_mutex_destroy(pthread_mutex_t *mutex);//互斥量加锁接口--阻塞式//说明:对共享资源的访问, 要对互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁。在完成了对共享资源的访问后, 要对互斥量进行解锁。int pthread_mutex_lock(pthread_mutex_t *mutex);//互斥量加锁接口--非阻塞式//说明: 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY,表示共享资源处于忙状态。int pthread_mutex_trylock(pthread_mutex_t *mutex);//互斥量解锁接口int pthread_mutex_unlock(pthread_mutex_t *mutex);//上述所有返回值: 成功则返回0, 出错则返回错误编号。

初始化:
在Linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化:
对于静态分配的互斥量,可以把它设置为PTHREAD_MUTEX_INITIALIZER,或者调用pthread_mutex_init;
对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化,并且在释放内存(free)前需要调用pthread_mutex_destroy;

死锁:
死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生。如何避免死锁是使用互斥量应该格外注意的东西。

总体来讲, 有几个不成文的基本原则:

  • 对共享资源操作前一定要获得锁。
  • 完成操作以后一定要释放锁。
  • 尽量短时间地占用锁。
  • 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
  • 线程错误返回时应该释放它所获得的锁。

各种Mutex的区别:

锁类型 初始化方式 加锁特征 调度特征 普通锁 PTHREAD_MUTEX_INITIALIZER 同一线程可重复加锁,解锁一次释放锁 先等待锁的进程先获得锁 嵌套锁 PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP 同一线程可重复加锁,解锁同样次数才可释放锁 先等待锁的进程先获得锁 纠错锁 PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP 同一线程不能重复加锁,加上的锁只能由本线程解锁 先等待锁的进程先获得锁 自适应锁 PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP 同一线程可重加锁,解锁一次生效 所有等待锁的线程自由竞争

代码:

/********************************Copyright Qinlong******************************* File   Name:  Mutex.cpp ** Create Date:  2016.11.15** Modify Time:  2016.11.16** Function:     mutex synchornization** Author:       qin long** Modifier:     **** Version:      1.0*******************************************************************************/#include <iostream>#include <pthread.h>#include <errno.h>using namespace std;//普通锁static pthread_mutex_t g_mutex=PTHREAD_MUTEX_INITIALIZER;//循环执行次数static const int g_iRunTime = 100;void* Fun(void* ptr){   int iRunTime = 0;   while(++iRunTime< g_iRunTime)   {      pthread_mutex_lock(&g_mutex);      cout << iRunTime << ": Fun() is running!" << endl;//    若下面一行代码不注释,则主函数输出会出现打印"main trylock failed!",//    原因就在于g_mutex锁被本线程函数长期占用的结果.//    usleep(200);        pthread_mutex_unlock(&g_mutex);      usleep(100000);    }}int main(){   pthread_t hHandle;   int iRet = pthread_create(&hHandle, NULL, Fun, NULL);    //create a thread;   if(0 != iRet)   {       cout << "Create thread failed!" << endl;   }   sleep(1);    int iRunTime = 0;   while(++iRunTime<g_iRunTime)   {        //这里仅仅是为了测试pthread_mutex_trylock的用法      if(EBUSY==pthread_mutex_trylock(&g_mutex))      {           cout<< "main trylock failed!"<<endl;           --iRunTime;      }      else      {           cout <<iRunTime<< ": main is running!" << endl;           pthread_mutex_unlock(&g_mutex);           usleep(100000);      }   }   pthread_join(hHandle, NULL);   return 0;} 

运行结果:
注释掉Fun中uSleep(200);的结果如下图所示,
这里写图片描述

未注释掉Fun中uSleep(200);的结果如下图所示,
这里写图片描述
这里运行结果出现了main trylock failed!原因是由于Fun函数在打印输出完毕后使用uSleep(200)“长时间占用”锁导致的,从使用pthread_mutex_trylock我们可以看到主函数在经过多次尝试进行加锁都失败了。因此我们的设计原则应该就是尽可能短时间去占用锁,才能提高多线程之间的运行以及同步效率。

0 0
原创粉丝点击