【多线程】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;
}
【多线程】C++ 互斥锁(mutex)的简单原理分析的更多相关文章
- 深入理解Solaris内核中互斥锁(mutex)与条件变量(condvar)之协同工作原理
在Solaris上写内核模块总是会用到互斥锁(mutex)与条件变量(condvar), 光阴荏苒日月如梭弹指一挥间,Solaris的大船说沉就要沉了,此刻心情不是太好(Orz).每次被年轻的有才华的 ...
- Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例
概要 本章介绍JUC包中的CyclicBarrier锁.内容包括:CyclicBarrier简介CyclicBarrier数据结构CyclicBarrier源码分析(基于JDK1.7.0_40)Cyc ...
- 互斥锁Mutex与信号量Semaphore的区别
转自互斥锁Mutex与信号量Semaphore的区别 多线程编程中,常常会遇到这两个概念:Mutex和Semaphore,两者之间区别如下: 有人做过如下类比: Mutex是一把钥匙,一个人拿了就可进 ...
- 线程锁(互斥锁Mutex)
线程锁(互斥锁Mutex) 一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况? # -*- cod ...
- Golang 读写锁RWMutex 互斥锁Mutex 源码详解
前言 Golang中有两种类型的锁,Mutex (互斥锁)和RWMutex(读写锁)对于这两种锁的使用这里就不多说了,本文主要侧重于从源码的角度分析这两种锁的具体实现. 引子问题 我一般喜欢带着问题去 ...
- 一文带你剖析LiteOS互斥锁Mutex源代码
摘要:多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的临界资源,只能被独占使用.LiteOS使用互斥锁来避免这种冲突,互斥锁是一种特殊的二值性信号量,用于实现对临界资源的独占 ...
- Linux内核互斥锁--mutex
一.定义: /linux/include/linux/mutex.h 二.作用及访问规则: 互斥锁主要用于实现内核中的互斥访问功能.内核互斥锁是在原子 API 之上实现的,但这对于内核用户是不可见 ...
- 线程锁(互斥锁Mutex)及递归锁
一.线程锁(互斥锁) 在一个程序内,主进程可以启动很多个线程,这些线程都可以访问主进程的内存空间,在Python中虽然有了GIL,同一时间只有一个线程在运行,可是这些线程的调度都归系统,操作系统有自身 ...
- java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...
随机推荐
- Python+Selenium自动化-设置等待三种等待方法
Python+Selenium自动化-设置等待三种等待方法 如果遇到使用ajax加载的网页,页面元素可能不是同时加载出来的,这个时候,就需要我们通过设置一个等待条件,等待页面元素加载完成,避免出现 ...
- Docker学习(1) 初识
Docker的使用场景 1 使用Docker容器开发,测试,部署服务 2 创建隔离的运行环境 3 搭建测试环境 4 构建多用户的平台及服务(PaaS)基础设施 5 提供软件即服务(SaaS)应用程序 ...
- Go语言流程控制03--goto跳转到任意标签位置
package main import ( "fmt" "time" ) func main() { STUDYHARD: fmt.Println(" ...
- Java中Map<Key, Value>存储结构根据值排序(sort by values)
需求:Map<key, value>中可以根据key, value 进行排序,由于 key 都是唯一的,可以很方便的进行比较操作,但是每个key 对应的value不是唯一的,有可能出现多个 ...
- 3 Python相对路径地址的的一个问题
构建程序xiaojie_test.py import os from xxx.yyy import test test() 同目录下构建一个目录xxx,并且目录中有/tmp/results/graph ...
- 视频处理单元Video Processing Unit
视频处理单元Video Processing Unit VPU处理全局视频处理,它包括时钟门.块复位线和电源域的管理. 缺少什么: •完全重置整个视频处理硬件块 •VPU时钟的缩放和设置 •总线时钟门 ...
- 开放式神经网络交换-ONNX(下)
开放式神经网络交换-ONNX(下) 计算节点由名称.它调用的算子operator的名称.命名输入的列表.命名输出的列表和属性列表组成. 输入和输出在位置上与算子operator输入和输出相关联.属性通 ...
- Ucore lab1实验报告
练习一 Makefile 1.1 OS镜像文件ucore.img 是如何一步步生成的? + cc kern/init/init.c + cc kern/libs/readline.c + cc ker ...
- Java设计模式(4:里氏替换原则和合成复用原则详解
一.里氏替换原则 如果说实现开闭原则的关键步骤就是抽象化,那么基类(父类)和子类的继承关系就是抽象化的具体实现,所以里氏替换原则就是对实现抽象化的具体步骤的规范.即:子类可以扩展基类(父类)的功能,但 ...
- B-Tree插入和删除的Java实现
B-Tree插入和删除的Java实现 一.一颗非空m阶B-Tree的性质 除根结点以外的每个结点的孩子引用最多存在m个,关键码最多存在m - 1个:除根结点以外的每个结点的孩子引用至少存在⌈m / 2 ...