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

  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)的简单原理分析的更多相关文章

  1. 深入理解Solaris内核中互斥锁(mutex)与条件变量(condvar)之协同工作原理

    在Solaris上写内核模块总是会用到互斥锁(mutex)与条件变量(condvar), 光阴荏苒日月如梭弹指一挥间,Solaris的大船说沉就要沉了,此刻心情不是太好(Orz).每次被年轻的有才华的 ...

  2. Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例

    概要 本章介绍JUC包中的CyclicBarrier锁.内容包括:CyclicBarrier简介CyclicBarrier数据结构CyclicBarrier源码分析(基于JDK1.7.0_40)Cyc ...

  3. 互斥锁Mutex与信号量Semaphore的区别

    转自互斥锁Mutex与信号量Semaphore的区别 多线程编程中,常常会遇到这两个概念:Mutex和Semaphore,两者之间区别如下: 有人做过如下类比: Mutex是一把钥匙,一个人拿了就可进 ...

  4. 线程锁(互斥锁Mutex)

    线程锁(互斥锁Mutex) 一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况? # -*- cod ...

  5. Golang 读写锁RWMutex 互斥锁Mutex 源码详解

    前言 Golang中有两种类型的锁,Mutex (互斥锁)和RWMutex(读写锁)对于这两种锁的使用这里就不多说了,本文主要侧重于从源码的角度分析这两种锁的具体实现. 引子问题 我一般喜欢带着问题去 ...

  6. 一文带你剖析LiteOS互斥锁Mutex源代码

    摘要:多任务环境下会存在多个任务访问同一公共资源的场景,而有些公共资源是非共享的临界资源,只能被独占使用.LiteOS使用互斥锁来避免这种冲突,互斥锁是一种特殊的二值性信号量,用于实现对临界资源的独占 ...

  7. Linux内核互斥锁--mutex

    一.定义: /linux/include/linux/mutex.h   二.作用及访问规则: 互斥锁主要用于实现内核中的互斥访问功能.内核互斥锁是在原子 API 之上实现的,但这对于内核用户是不可见 ...

  8. 线程锁(互斥锁Mutex)及递归锁

    一.线程锁(互斥锁) 在一个程序内,主进程可以启动很多个线程,这些线程都可以访问主进程的内存空间,在Python中虽然有了GIL,同一时间只有一个线程在运行,可是这些线程的调度都归系统,操作系统有自身 ...

  9. java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...

随机推荐

  1. Python发送SMTP邮件指南

      SMTP(Simple Mail Transfer Protocol)简单邮件传输协议,Python内置对SMTP的支持,可以发送纯文本文件,HTML邮件以及附带文件.   一.两个模块 Pyth ...

  2. C# HTTP请求对外接口、第三方接口公用类

    /// <summary> /// 网络数据请求公共函数 /// </summary> public class HttpWebRequestCommon { #region ...

  3. VTA硬件

    VTA硬件 提供了VTA硬件设计的自上而下的概述.本硬件设计涵盖两个级别的VTA硬件: VTA设计及其ISA硬件-软件接口的体系结构概述. VTA硬件模块的微体系结构概述以及计算核心的微代码规范. V ...

  4. Linux架构思维导图

    Linux架构思维导图 GUI(Graphical User Interface,图形用户界面) Linux学习路径 软件框架 Linux桌面介绍 FHS:文件系统目录标准 Linux需要特别注意的目 ...

  5. TinyML设备设计的Arm内核

    TinyML设备设计的Arm内核 Arm cores designed for TinyML devices Arm推出了两个新的IP核,旨在为终端设备.物联网设备和其低功耗.成本敏感的应用程序提供机 ...

  6. 孟老板 BaseAdapter封装(四) PageHelper

    BaseAdapter封装(一) 简单封装 BaseAdapter封装(二) Header,footer BaseAdapter封装(三) 空数据占位图 BaseAdapter封装(四) PageHe ...

  7. java面试必知必会——排序

    二.排序 时间复杂度分析 排序算法 平均时间复杂度 最好 最坏 空间复杂度 稳定性 冒泡 O(n²) O(n) O(n²) O(1) 稳定 选择 O(n²) O(n²) O(n²) O(1) 不稳定 ...

  8. 九、配置Tomcat集群

    配置Tomcat集群所需服务器三台:192.168.1.5(调度服务器).192.168.1.10(WEB1),192.168.1.20(WEB2) 1.调度服务器设置 [root@proxy ~]# ...

  9. 从一条sql报错解决过程学习程序员查bug的思路

    从oracle迁移数据到达梦后,发现数据库默认值都丢失了.于是我想从oracle数据库将默认值查出来,在达梦数据库加回去. 于是上网查了一下,看怎么获取oracle数据库字段默认值信息,找到了这个sq ...

  10. FlinkSQL写入Kafka/ES/MySQL示例-JAVA

    一.背景说明 Flink的API做了4层的封装,上两层TableAPI.SQL语法相对简单便于编写,面对小需求可以快速上手解决,本文参考官网及部分线上教程编写source端.sink端代码,分别读取s ...