多线程(三)多线程同步_基本介绍及mutex互斥体
同步进制的引入为了解决以下三个主要问题:
1.控制多个线程之间对共享资源访问,保证共享资源的完整性
例如:线程A对共享资源进行写入,线程B读取共享资源
2.确保多个线程之间的动作以指定的次序发生
例如:线程B以线程A结束为条件进行触发运行
3.控制共享资源的最大访问数量
例如:有10个线程需要访问共享资源,同时只允许5个线程访问,那剩余线程就需要放入队列中进行等待
同步对象分类:
用户模式下的同步对象:例如关键段等
优点:速度快
缺点:不能跨进程,容易引起死锁
内核模式下的同步对象:例如互斥量,信号量,事件等
优点:跨进程,安全性高
缺点:速度慢(需要从当前用户模式下切换到内核模式,需要消耗时间)
等待函数(同步函数)
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
1.hHandle 对象句柄,如Event(事件)、Mutex(互斥锁)、Semaphore(信号)、Thread(线程)、process(进程)等等内核对象句柄
2.dwMilliseconds 等待时间间隔,毫秒
如果dwMilliseconds为非零值,函数会处于等待状态直到hHandle标志的对象成为触发状态,或者时间超时,函数才会返回
如果dwMilliseconds为0值,不论hHandle标志的对象是否是触发状态,都会立即返回
如果dwMilliseconds为INFINITE,函数会无限等待直到hHandle标志的对象成为触发状态后才会返回
返回值:
WAIT_OBJECT_0 对象是触发状态
WAIT_TIMEOUT 等待超时,等待时间内对象一直处于未触发状态
WAIT_ABANDONED 拥有mutex的线程在结束时没有释放对象
WAIT_FAILED 传递了一个无效句柄,出现错误,可通过GetLastError得到错误代码
注意:
1.调用等待函数时,会挂起当前代码所处的线程,直到函数返回时线程才会恢复执行.避免在窗口线程中调用此函数,因为一旦调用,窗口线程被挂起,函数没有返回前整个窗口都是处于假死状态。
2.当第一个参数是一个mutex对象时,有以下三种情况:
一.如果mutex对象存在拥有者并且不是当前线程,就会根据第二个参数进行等待.根据返回值如果为WAIT_OBJECT_0代表当前线程获得对象拥有权。如果为WAIT_TIMEOUT代表超时未获得对象拥有权。如果为WAIT_ABANDONED代表拥有mutex的线程在结束时没有释放对象,此时无法获知通过mutex所保护的共享资源是否被破坏.
二.如果mutex对象不存在拥有者,当前线程将会自动成为新的拥有者,返回WAIT_OBJECT_0
三.如果mutex对象拥有者是当前线程,也是直接返回WAIT_OBJECT_0
每次调用WaitForSingleObject,如果返回值为WAIT_OBJECT_0,mutex对象内部的递归计数器会+1,后面一定要使用ReleaseMutex进行释放使递归计数器-1.
演示:检测某个进程是否结束
//不进行等待,只获取进程是否有信号
//注意:进程启动后是无信号状态,只有当点击关闭按钮进程退出时才会变为有信号状态
DWORD dwCode = WaitForSingleObject(hProcess,);
switch (dwCode)
{
case WAIT_OBJECT_0: //有信号状态,表示进程己退出
printf("Process is Exit!\r\n");
break;
case WAIT_TIMEOUT: //无信号状态,表示进程还在运行中
printf("Process is Running\r\n");
break;
case WAIT_FAILED: //错误
{
DWORD dwError = GetLastError();
printf("Fail:%d\r\n",dwError);
}
default:
break;
}
CloseHandle(hProcess); getchar();
return ;
演示1
mutex互斥对象:
它是系统内核中的一种数据结构,用于确保一个线程独占一个资源的访问.
包含三个部分:
使用计数器 统计有多少个线程在调用用该对象
线程ID 当前占用这个互斥对象的线程ID.线程ID为0,代表没有被任何线程占用,处于触发状态.反之线程ID不为0,代表被一个线程占用,处于未触发状态
递归计数器 统计当前占用这个互斥对象的线程占用了该互斥对象的次数
创建互斥体对象
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner,LPCTSTR lpName)
1.lpMutexAttributes 指向安全属性指针,通常为NULL
2.bInitialOwner 初始化互斥对象所有者,通常为FASLE,也就是没有所有者
3.lpName 指定互斥体对象的名字
返回值:
成功返回新互斥体对象句柄,失败返回0
注意:如果己经有同名的互斥体对象存在,函数将返回己有的互体斥对象句柄,不会再创建新的互斥体对象
释放互斥体对象
BOOL ReleaseMutex(HANDLE hMutex)
1.hMutex 互斥体对象句柄
返回值:
成功返回真,失败返回假
当线程中调用ReleaseMutex时,该函数会判断当前线程ID与互斥体对象的线程ID是否相同?如果相同,互斥体对象的递归计数器会-1,如果不相同不进行任何操作,直接返回FASLE,此时调用GetLastErro,将返回ERROR_NOT_OWNER(试图释放不是调用者拥有的互斥对象)
每调用一次该函数会使对象的占用计数器-1.如果线程调用等待函数返回WAIT_OBJECT_0不止一次,那ReleaseMutex就得调用相同次数,才能使占用计数器变为0.当占用计数器变为0时,系统内部会把该互斥体对象的线程ID设为0,也就是失去拥有者,此时该互斥对象成为可触发状态.
注意:如果一个线程是互斥体对象的拥有者,但是没有释放互斥对象就结束了线程,此后系统在检则互斥体对象的线程ID时发现此线程己结束,系统将自动把互斥对象的ID复置为0,并将它的递归计数器复置为0.然后从等待线程中寻找一个线程做为它的新的拥有者,并将递归计数器设置为1,同时这个等待线程变为可调度状态
使用互斥体对象配合等待函数实现同步的流程:
假设有一个全局的共享资源,同时有多个线程进行读写。为了保证共享资源的完整性,同一时刻只允许一个线程读写操作
1.创建一个互斥体对象,第二个参数为0,代表该互斥体对象没有所有者,保存返回的对象句柄供多个线程访问
2.多个线程内部在访问共享资源前,先调用WaitForSingleObject传入保存的互斥体对象句柄,然后再访问共享资源,最后再ReleaseMutex.
这样当有一个线程A执行WaitForSingleObject时,会获得互斥体对象所有权,此时互斥体对象的线程ID将被修改为线程A的ID,然后递归计数器为1,使用计数器为1,然后线程A独占访问共享资源。在没有调用ReleaseMutex前,后面调用WaitForSingleObject的线程由于对象是未触发状态,最终都将进入等待状态.直到线程A调用ReleaseMutex使递归计数器-1后变为0,此时系统会把线程ID设为0,交出所有权,该对象又成为触发状态,下一个线程才能获得所有权,独占访问共享资源。
编写一个Demo用于演示mutex互斥体基本操作
功能介绍:
模拟售票系统,有三个售票窗口(三个线程),每隔1秒售出1张票,售票前提示剩余总票数,以及提示售出1张票。当剩余总票售为0时,提示售票窗口关闭(线程结束)
开始编写代码:
1. 创建个基于对话框的工程MutexDemo
2. 添加一个编辑框用于显示售票信息,修改ID为IDC_EDIT_SHOWINFO, 修改属性为不可读.
3.添加一个按钮用于启动线程,修改ID为IDC_BTN_SELLTICKET
4. 先定义相关全局变量和线程函数前置声明
int g_nTickNum = ; //总票数
CEdit* g_editShowInfo; //编辑框控件指针
HANDLE g_hMutex; //互斥体对象句柄 //线程函数前置声明
DWORD WINAPI ThreadSellTicket_One(LPVOID lpParam);
DWORD WINAPI ThreadSellTicket_Two(LPVOID lpParam);
DWORD WINAPI ThreadSellTicket_Three(LPVOID lpParam);
全局变量和线程函数前置声明
5.OnInitDialog中添加相应代码
BOOL CMutexDemoDlg::OnInitDialog()
{
CDialogEx::OnInitDialog(); // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标 //创建mutex互斥体对象
g_hMutex = CreateMutex(NULL,FALSE,);
//获取EDIT控件指针,供线程内部访问
g_editShowInfo = (CEdit*)GetDlgItem(IDC_EDIT_SHOWINFO); return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
OnInitDialog
6.按钮_启动线程事件
//按钮_开始售票
void CMutexDemoDlg::OnBnClickedBtnSellticket()
{
//创建三个售票窗口线程,创建后直接关闭句柄
CloseHandle(CreateThread(NULL,,ThreadSellTicket_One,NULL,,NULL));
CloseHandle(CreateThread(NULL,,ThreadSellTicket_Two,NULL,,NULL));
CloseHandle(CreateThread(NULL,,ThreadSellTicket_Three,NULL,,NULL));
}
按钮_启动线程事件
7.三个售票窗口线程代码
DWORD WINAPI ThreadSellTicket_One(LPVOID lpParam)
{
CString strEdit;
CString strNew;
while (true)
{
Sleep();
WaitForSingleObject(g_hMutex,INFINITE);
g_editShowInfo->GetWindowText(strEdit);
if(g_nTickNum > )
{
strNew.Format(_T("线程1:剩余票数:%d,售出1张票\r\n"),g_nTickNum--);
strEdit += strNew;
if(g_nTickNum == )
{
strEdit += _T("线程1:票己售空,关闭售票窗口\r\n");
g_editShowInfo->SetWindowText(strEdit);
ReleaseMutex(g_hMutex);
break;
}
else
{
g_editShowInfo->SetWindowText(strEdit);
ReleaseMutex(g_hMutex);
}
}
else
{
strEdit += _T("线程1:票己售空,关闭售票窗口\r\n");
g_editShowInfo->SetWindowTextW(strEdit);
ReleaseMutex(g_hMutex);
break;
}
}
return true;
}
线程_售票窗口1
DWORD WINAPI ThreadSellTicket_Two(LPVOID lpParam)
{
CString strEdit;
CString strNew;
while (true)
{
Sleep();
WaitForSingleObject(g_hMutex,INFINITE);
g_editShowInfo->GetWindowText(strEdit);
if(g_nTickNum > )
{
strNew.Format(_T("线程2:剩余票数:%d,售出1张票\r\n"),g_nTickNum--);
strEdit += strNew;
if(g_nTickNum == )
{
strEdit += _T("线程2:票己售空,关闭售票窗口\r\n");
g_editShowInfo->SetWindowText(strEdit);
ReleaseMutex(g_hMutex);
break;
}
else
{
g_editShowInfo->SetWindowText(strEdit);
ReleaseMutex(g_hMutex);
}
}
else
{
strEdit += _T("线程2:票己售空,关闭售票窗口\r\n");
g_editShowInfo->SetWindowTextW(strEdit);
ReleaseMutex(g_hMutex);
break;
}
}
return true;
}
线程_售票窗口2
DWORD WINAPI ThreadSellTicket_Three(LPVOID lpParam)
{
CString strEdit;
CString strNew;
while (true)
{
Sleep();
WaitForSingleObject(g_hMutex,INFINITE);
g_editShowInfo->GetWindowText(strEdit);
if(g_nTickNum > )
{
strNew.Format(_T("线程3:剩余票数:%d,售出1张票\r\n"),g_nTickNum--);
strEdit += strNew;
if(g_nTickNum == )
{
strEdit += _T("线程3:票己售空,关闭售票窗口\r\n");
g_editShowInfo->SetWindowText(strEdit);
ReleaseMutex(g_hMutex);
break;
}
else
{
g_editShowInfo->SetWindowText(strEdit);
ReleaseMutex(g_hMutex);
}
}
else
{
strEdit += _T("线程3:票己售空,关闭售票窗口\r\n");
g_editShowInfo->SetWindowTextW(strEdit);
ReleaseMutex(g_hMutex);
break;
}
}
return true;
}
线程_售票窗口3
8.DestroyWindow添加相应代码
BOOL CMutexDemoDlg::DestroyWindow()
{
//关闭互斥体对象句柄
CloseHandle(g_hMutex); return CDialogEx::DestroyWindow();
}
DestroyWindow
最终演示效果如下:
多线程(三)多线程同步_基本介绍及mutex互斥体的更多相关文章
- Java多线程(三) 多线程间的基本通信
多条线程在操作同一份数据的时候,一般需要程序去控制好变量.在多条线程同时运行的前提下控制变量,涉及到线程通信及变量保护等. 本博文主要总结:①线程是如何通信 ②如何保护线程变量 1.Java里的线程 ...
- Delphi多线程编程--线程同步的方法(事件、互斥、信号、计时器)简介
更详细的可以参考:http://www.cnblogs.com/xumenger/p/4450659.html 或者参考之后的博客 四个系统内核对象(事件.互斥.信号.计时器)都是线程同步的手段,从这 ...
- Java 并发和多线程(三) 多线程的代价 [转]
原文链接:http://tutorials.jenkov.com/java-concurrency/costs.html 作者:Jakob Jenkov 翻译:古圣昌 校对:欧振 ...
- 多线程锁:Mutex互斥体,Semaphore信号量,Monitor监视器,lock,原子操作InterLocked
Mutex类 “mutex”是术语“互相排斥(mutually exclusive)”的简写形式,也就是互斥量.互斥量跟临界区中提到的Monitor很相似,只有拥有互斥对象的线程才具有访问资源的权限, ...
- c# Thread5——线程同步之基本原子操作。Mutex互斥量的使用
之前的博文也说到了如果多线程对于访问的公共资源操作都是原子操作,那么可以避免竞争条件.关于多线程的竞争可以百度. 1.执行最基本的原子操作 c#提供了一系列供我们使用的原子操作的方法和类型,比如我们的 ...
- 多线程(四)多线程同步_Critical Section临界区
临界区是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权.和使用mutex一样,它们都是以原子操作方式来对共享资源进行访问. 临界区又叫关键代码段,与上一篇的mutex互斥体实现的功 ...
- IOS 多线程,线程同步的三种方式
本文主要是讲述 IOS 多线程,线程同步的三种方式,更多IOS技术知识,请登陆疯狂软件教育官网. 一般情况下我们使用线程,在多个线程共同访问同一块资源.为保护线程资源的安全和线程访问的正确性. 在IO ...
- {python--GIL锁}一 介绍 二 GIL介绍 三 GIL与Lock 四 GIL与多线程 五 多线程性能测试
python--GIL锁 GIL锁 本节目录 一 介绍 二 GIL介绍 三 GIL与Lock 四 GIL与多线程 五 多线程性能测试 一 背景知识 ''' 定义: In CPython, the gl ...
- C++多线程同步技巧(三)--- 互斥体
简介 Windows互斥对象机制. 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问,在线程同步与保证程序单体运行上都有相当大的用处. 代码 ...
随机推荐
- <DP> (高频)139 375 374 (DP hard)312
139. Word Break 返回结果较为简单可用dp, 复杂用dfs class Solution { public boolean wordBreak(String s, List<Str ...
- Web前端开发框架大全-详述
可以说,前端技术的发展是互联网自身发展的一个缩影! 前端技术的发展经历了web1.0时代,即网页只能展示信息,几乎没有交互可言: web2.0时代,web2.0不再是单维的,逐渐发展为双向交流,另一特 ...
- jboss_log4j.xml配置
log4j是个优秀的开源的java日志系统,jboss内部也集成他,在jboss下默认的只是对server做了每日日志,并没有对你部署的项目进行每日的日志构建,但我们能通过修改jboss-log4j. ...
- 造轮子ArrayList
这篇博客实现一个简单的ArrayList集合.博客里的代码首先根据自己的想法实现,在走不动的情况下会去参考JDK源代码.所以阅读本文,不要抱着跟JDK源码对比的心态.于我个人而言,国庆期间,纯属娱乐. ...
- Android系统之LK启动流程分析(一)
1.前言 LK是Little Kernel的缩写,在Qualcomm平台的Android系统中普遍采用LK作为bootloader,它是一个开源项目,LK是整个系统的引导部分,所以不是独立存在的,但是 ...
- border和outline的区别
如果有一个需求,给一个元素增加一条边框,想必大家会习惯且娴熟的使用border来实现,我也是这样 但其实outline也能达到同样的效果,并且在有些场景下会更适用,比如下面的demo 使用bord ...
- [线段树]Luogu P3372 线段树 1【模板】
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #d ...
- 解决 Visual Studio 符号加载不完全问题
解决 Visual Studio 符号加载不完全问题 工具 - 选项 - 搜索 "符号" - 选上服务器 | 加载所有符号, 之后符号就会显示完全
- C#,File.AppendAllLines(),换行"\r\n"
string sourcePath = @"D:\GL\20160826141915999999.txt"; for (int i = 0; i < 10; i++) { G ...
- Docker(Linux)学习笔记以及Redis/MariaDB的容器使用后台全自动启动
1:Docker安装,由于Docker后续pull镜像的服务器默认是在国外的,速度实在是太慢,这里使用阿里云的镜像 阿里云的Docker CE 镜像源站进行安装 docker ===========U ...