临界区是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权。和使用mutex一样,它们都是以原子操作方式来对共享资源进行访问。

临界区又叫关键代码段,与上一篇的mutex互斥体实现的功能一样,都是为了让多线程同步

从上面图片可以看到二者的区别,如果是在当前进程进行线程同步,只需要采用临界区即可

如果需要跨进程,就采用互斥体,最常用的一种情况就是通过在程序初始化时先打开斥体对象,如果失败就创建一个互斥体对象,反之就结束进程,以此来保证程序在任意时间只运行一份

临界区中所有API都需要传递一个C R I T I C A L _ S E C T I O N结构指针,这个结构不需要对其中成员进行赋值,只需要定义。当使用相关API时,传递结构地址即可

注意:在进入临界区和退出临界区之间的代码都处于加锁受保护状态,千万不要在其间写sleep之类的函数,否则可能出现死锁

初始化临界区
VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);
1. C R I T I C A L _ S E C T I O N结构指针
该函数用于对结构的各个成员进行初始化. 注意:当不使用临界区时,需要删除临界区

删除临界区
VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);
1. C R I T I C A L _ S E C T I O N结构指针
该函数用于对结构中的成员变量进行删除做清理工作。如果有任何线程仍然使用关键代码段,那么不应该删除该代码段

进入临界区
VOID EnterCriticalSection(PCRITICAL_SECTION pcs);
1. C R I T I C A L _ S E C T I O N结构指针
这个函数功能与WaitForSingleObject类相似,它会检测CRITICAL_SECTION中成员根据不种情况做不同操作

1. 如果没有线程拥有访问权,就会修改成员使之指向当前线程,访问计数为1,使当前线程拥有对CRITICAL_SECTION的所有权并且立即返回
2. 如果拥有访问权的是当前线程,就会把访问计数+1. 如果出现这种情况,说明当前线程在某一时刻进入了临界区,但是没有退出临界区,又重新进入了临界区,明显是个错误的操作
3.如果拥有访问权的是其它线程,就会进入等待状态,直到得到拥有权变有可调度状态才会苏醒

注意:当线程进入临界区,不再访问共享资源时,需要退出临界区,否则将出现第2种错误的操作发生

退出离界区
VOID LeaveCriticalSection(PCRITICAL_SECTION pcs);
1.PCRITICAL_SECTION

它会检测CRITICAL_SECTION中成员,如果为0,将不做任何操作直接返回. 如果计数大于0,就-1,如果结果为0,查找等待的线程数量是否大于0?
如果大于1,就从所有等待线中挑选一个线程成为新的拥有者,修改计数器为1,然后返回. 反之如果等待线程数量为0,直接返回

编写一个Demo用于演示Critical Section临界区基本操作,功能与上一篇相同

1. 创建个基于对话框的工程CriticalSectionDemo

2. 添加一个编辑框用于显示售票信息,修改ID为IDC_EDIT_SHOWINFO, 修改属性为不可读.

3.添加一个按钮用于启动线程,修改ID为IDC_BTN_SELLTICKET

4. 先定义相关全局变量和线程函数前置声明

 int g_nTickNum = ; //总票数
CEdit* g_editShowInfo; //编辑框控件指针
CRITICAL_SECTION g_cs; //临界区对象 //线程函数前置声明
DWORD WINAPI ThreadSellTicket_One(LPVOID lpParam);
DWORD WINAPI ThreadSellTicket_Two(LPVOID lpParam);
DWORD WINAPI ThreadSellTicket_Three(LPVOID lpParam);

全局变量和线程函数前置声明

5.OnInitDialog中添加相应代码

 BOOL CCriticalSectionDemoDlg::OnInitDialog()
{
CDialogEx::OnInitDialog(); // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标 //初始化临界区
InitializeCriticalSection(&g_cs);
//获取EDIT控件指针,供线程内部访问
g_editShowInfo = (CEdit*)GetDlgItem(IDC_EDIT_SHOWINFO); return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}

OnInitDialog

6.按钮_启动线程事件

 void CCriticalSectionDemoDlg::OnBnClickedBtnSellticket()
{
//创建三个售票窗口线程,创建后直接关闭句柄
CloseHandle(CreateThread(NULL,,ThreadSellTicket_One,NULL,,NULL));
CloseHandle(CreateThread(NULL,,ThreadSellTicket_Two,NULL,,NULL));
CloseHandle(CreateThread(NULL,,ThreadSellTicket_Three,NULL,,NULL));
}

按钮_启动线程事件

7.三个售票窗口线程代码

 //线程_售票窗口1
DWORD WINAPI ThreadSellTicket_One(LPVOID lpParam)
{
CString strEdit;
CString strNew;
while (true)
{
Sleep();
EnterCriticalSection(&g_cs);
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);
LeaveCriticalSection(&g_cs);
break;
}
else
{
g_editShowInfo->SetWindowText(strEdit);
LeaveCriticalSection(&g_cs);
}
}
else
{
strEdit += _T("线程1:票己售空,关闭售票窗口\r\n");
g_editShowInfo->SetWindowTextW(strEdit);
LeaveCriticalSection(&g_cs);
break;
}
}
return true;
}

线程_售票窗口1

 DWORD WINAPI ThreadSellTicket_Two(LPVOID lpParam)
{
CString strEdit;
CString strNew;
while (true)
{
Sleep();
EnterCriticalSection(&g_cs);
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);
LeaveCriticalSection(&g_cs);
break;
}
else
{
g_editShowInfo->SetWindowText(strEdit);
LeaveCriticalSection(&g_cs);
}
}
else
{
strEdit += _T("线程2:票己售空,关闭售票窗口\r\n");
g_editShowInfo->SetWindowTextW(strEdit);
LeaveCriticalSection(&g_cs);
break;
}
}
return true;
}

线程_售票窗口2

 DWORD WINAPI ThreadSellTicket_Three(LPVOID lpParam)
{
CString strEdit;
CString strNew;
while (true)
{
Sleep();
EnterCriticalSection(&g_cs);
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);
LeaveCriticalSection(&g_cs);
break;
}
else
{
g_editShowInfo->SetWindowText(strEdit);
LeaveCriticalSection(&g_cs);
}
}
else
{
strEdit += _T("线程3:票己售空,关闭售票窗口\r\n");
g_editShowInfo->SetWindowTextW(strEdit);
LeaveCriticalSection(&g_cs);
break;
}
}
return true;
}

线程_售票窗口3

8.DestroyWindow添加相应代码

 BOOL CCriticalSectionDemoDlg::DestroyWindow()
{
//删除临界区
DeleteCriticalSection(&g_cs);
return CDialogEx::DestroyWindow();
}

DestroyWindow

最终演示效果如下:

多线程(四)多线程同步_Critical Section临界区的更多相关文章

  1. java多线程(四)之同步机制

    1.同步的前提 多个线程 多个线程使用的是同一个锁 2.同步的好处 同步的出现解决了多线程的安全问题 3.同步的弊端 当线程较多时, 因为每个线程都会去判断同步上的锁, 这样是很耗费资源的, 会降低程 ...

  2. C#多线程之线程同步篇2

    在上一篇C#多线程之线程同步篇1中,我们主要学习了执行基本的原子操作.使用Mutex构造以及SemaphoreSlim构造,在这一篇中我们主要学习如何使用AutoResetEvent构造.Manual ...

  3. Linux多线程编程——多线程与线程同步

    多线程 使用多线程好处: 一.通过为每种事件类型的处理单独分配线程,可以简化处理异步事件的代码,线程处理事件可以采用同步编程模式,启闭异步编程模式简单 二.方便的通信和数据交换 由于进程之间具有独立的 ...

  4. C#简单多线程使用(同步和优先权)

    题目: 麦当劳有两个做汉堡的厨师(工号:11,12)和三个销售人员(工号:21,22,23). 厨师生产汉堡,并负责将做好的汉堡放入货架,货架台大小有限,最多放6个汉堡,11和12不能同时往货架台上放 ...

  5. 关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

    Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...

  6. 【delphi】多线程与多线程同步

    在 Delphi 中使用多线程有两种方法: 调用 API.使用 TThread 类; 使用 API 的代码更简单. CreateThread function CreateThread( lpThre ...

  7. 数据结构(逻辑结构,物理结构,特点) C#多线程编程的同步也线程安全 C#多线程编程笔记 String 与 StringBuilder (StringBuffer) 数据结构与算法-初体验(极客专栏)

    数据结构(逻辑结构,物理结构,特点) 一.数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.逻辑结构包括: 集合 数 ...

  8. C# 多线程之线程同步

    多线程间应尽量避免同步问题,最好不要线程间共享数据.如果必须要共享数据,就需要使用同步技术,确保一次只有一个线程访问和改变共享状态. 一::lock语句 lock语句事设置锁定和接触锁定的一种简单方法 ...

  9. C/C++ 实现多线程与线程同步

    多线程中的线程同步可以使用,CreateThread,CreateMutex 互斥锁实现线程同步,通过临界区实现线程同步,Semaphore 基于信号实现线程同步,CreateEvent 事件对象的同 ...

随机推荐

  1. Markdown & LaTex 常用语法

    目录 blog 的目录 博客园自带目录 用 javascript 自定义目录 主标题 副标题 h1,一级标题 h2,二级标题 h3,三级标题 注释 常用的符号及文本形式 如果你想在markdown中文 ...

  2. jQuery中的工具(十)

    1. jQuery.each(object, [callback]), 通用遍历方法,可用于遍历对象和数组 不同于遍历 jQuery 对象的 $().each() 方法,此方法可用于遍历任何对象.回调 ...

  3. 读取本地文件转化成MultipartFile

    介绍 现在有个上传文件功能,需要将文件上传到oss上,但是文件有点多,于是使用接口进行上传.但是需要上传文件转换为MultipartFile类型文件进行上传. 主要代码 添加pom文件 <dep ...

  4. 43 树莓派安装pytorch

    狗狗 https://www.pytorchtutorial.com/pytorch-c-api-4-cat-dog-classifier-2/ 鲜花分类器 https://www.pytorchtu ...

  5. python数据分析教程大全

    第一篇:Anaconda安装和使用 第二篇:Jupyter norebook使用 第三篇:pandas教程 第四篇:numpy教程 第五篇:Matplotlib教程 第六篇:实战项目 期待吗?(微笑脸 ...

  6. 在windows上搭建git服务器教程

    1.首先,需要确保windows系统上安装并配置了Java运行环境,JDK>=1.7. 2.下载Gitblit,下载地址:http://www.gitblit.com/ 3.解压缩下载的压缩包即 ...

  7. Windows重要的win键

    win+↓ 当前窗口操作,多按几下就缩没了(同理,其他箭头也一样) win+e 打开此电脑 win+v 展开剪切板 win+k 访问蓝牙 win+a win10的通知 win+d (切到桌面,再用能切 ...

  8. IDEA 日常小技巧

    原文首发于 studyidea.cn点击查看更多技巧 适用于 IDEA 2019.2 之前版本 ,2019.2 版本以下功能默认开启. Surround a selection with a quot ...

  9. linux中查找包含指定内容的文件

    Linux查找文件内容的常用方法 ##文件名+内容 grep -r "查询内容" 文件目录 ##只显示包含内容的文件名 grep -r -l "查询内容" 文件 ...

  10. 使用 Xbox Game 录制桌面视频(录制音频)

    使用 Xbox Game 录制桌面视频(附带音频) 前言:可能自己音频输出的问题,一直无法用工具录制桌面的音频,而最后发现利用 Xbox Game 录制游戏视频的功能很好地解决我们的问题. 1)打开游 ...