原文链接: http://blog.csdn.net/olansefengye1/article/details/53086141

一、互斥量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我们可以看到主函数在经过多次尝试进行加锁都失败了。因此我们的设计原则应该就是尽可能短时间去占用锁,才能提高多线程之间的运行以及同步效率。

C++多线程同步之Mutex(互斥量)的更多相关文章

  1. Linux并发与同步专题 (4) Mutex互斥量

    关键词:mutex.MCS.OSQ. <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Linux并发与同步 ...

  2. linux 线程的同步 一 (互斥量和信号量)

    互斥量(Mutex) 互斥量表现互斥现象的数据结构,也被当作二元信号灯.一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源. ...

  3. 【转】windows平台多线程同步之Mutex的应用

    线程组成:  线程的内核对象,操作系统用来管理该线程的数据结构. 线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量.   操作系统为每一个运行线程安排一定的CPU时间 —— 时间片.系统通 ...

  4. Linux的线程同步对象:互斥量Mutex,读写锁,条件变量

        进程是Linux资源分配的对象,Linux会为进程分配虚拟内存(4G)和文件句柄等 资源,是一个静态的概念.线程是CPU调度的对象,是一个动态的概念.一个进程之中至少包含有一个或者多个线程.这 ...

  5. 线程同步方式之互斥量Mutex

    互斥量和临界区非常相似,只有拥有了互斥对象的线程才可以访问共享资源,而互斥对象只有一个,因此可以保证同一时刻有且仅有一个线程可以访问共享资源,达到线程同步的目的. 互斥量相对于临界区更为高级,可以对互 ...

  6. c# Thread5——线程同步之基本原子操作。Mutex互斥量的使用

    之前的博文也说到了如果多线程对于访问的公共资源操作都是原子操作,那么可以避免竞争条件.关于多线程的竞争可以百度. 1.执行最基本的原子操作 c#提供了一系列供我们使用的原子操作的方法和类型,比如我们的 ...

  7. 【Linux】Mutex互斥量线程同步的例子

    0.互斥量  Windows下的互斥量 是个内核对象,每次WaitForSingleObject和ReleaseMutex时都会检查当前线程ID和占有互斥量的线程ID是否一致. 当多次Wait**时就 ...

  8. php Pthread 多线程 (三) Mutex 互斥量

    当我们用多线程操作同一个资源时,在同一时间内只能有一个线程能够对资源进行操作,这时就需要用到互斥量了.比如我们对同一个文件进行读写操作时. <?php class Add extends Thr ...

  9. windows多线程(六) 互斥量Mutex与关键段CriticalSection比较

    一.关键段CS 和 互斥量Mutex 的相同点:都有线程拥有权 关键段和互斥量都有线程拥有权,即可以被一个线程拥有.在 前面讲关键段CS的文章中有说到,关键段结构体的第四个参数保存着拥有该关键段的线程 ...

随机推荐

  1. 解决Ubuntu下在firefox中打开Microsoft Outlook Web Access中文乱码

    Edit---Preference--Content--Languages--Choose...---Select a langue to add... 添加中文

  2. grafana dashboard的导入导出

    grafana的官方提供了很多社区或者官方设置的漂亮的dashboard,地址如下: 点击打开链接 导入图表大大节省了我们配置监控的时间,非常方便. 以linux host overview为例,首先 ...

  3. NBUT 1224 Happiness Hotel 2010辽宁省赛

    Time limit 1000 ms Memory limit 131072 kB The life of Little A is good, and, he managed to get enoug ...

  4. 京东Java面试题(二)

    1.set集合从原理上如何保证不重复 1)在往set中添加元素时,如果指定元素不存在,则添加成功.也就是说,如果set中不存在(e==null ? e1==null : e.queals(e1))的元 ...

  5. word-如何将文字设置为插入超链接

    前言 使用word有时候想要将文字部分设置为插入超链接,本文对此进行介绍. 操作步骤 1. 输入需要插入链接的文字部分: 2. 选中文字部分单击右键,点击超链接进行插入: 具体操作如下图所示: 参考 ...

  6. CodeForces - 325E:The Red Button (哈密尔顿 转 欧拉回路)

    Piegirl found the red button. You have one last chance to change the inevitable end. The circuit und ...

  7. AngularJS的简单订阅发布模式例子

    控制器之间的交互方式广播 broadcast, 发射 emit 事件 类似于 js中的事件 , 可以自己定义事件 向上传递直到 document 在AngularJs中 向上传递直到 rootScop ...

  8. PHP webservice初探

    背景:在最近的开发中,为了解决公司内部系统与外部系统的对接,开始接触到了webservice接口,外部公司提供接口供我们调用,已达到数据同步的目的,因此有必要普及一下web service的知识了! ...

  9. git代码回退

    情况1.还没有push可能 git add ,commit以后发现代码有点问题,想取消提交,用: reset git reset [--soft | --mixed | --hard] eg:  gi ...

  10. ElasticSearch 5.0 简介

    参考:http://blog.csdn.net/wzhg0508/article/details/52063676 Elasticsearch 5.0 简介(medcl微信直播实录) 大家好,非常高兴 ...