临界区是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权。和使用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. 关于appium的简单理解

    搭建好appium环境后,要学会定位app页面上的元素.下面是2款元素定位工具 uiautomatorviewer   -- Android SDK自带的元素定位工具,由Google开发的 Inspe ...

  2. python27期尚哥讲数据库:

    1.下载:https://www.mysql.com/ DOWNLOADS MySQL Community (GPL) Downloads »-- MySQL Community Server Loo ...

  3. window.location.href方式提交json数据

    ${ctx}/vehicleFlow/to_vehflow_detail.do?strJson="+encodeURIComponent(json)

  4. postgres 计算时差

    计算时间差秒数 select extract(epoch FROM (now() - (now()-interval '1 day') ));

  5. docker 创建mysql容器并且绑定到本地navicat

    docker pull mysql docker run --name mysql -itd -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root1234 mysql do ...

  6. web 服务

    package main import ( "strings" "fmt" "net/http" "log" ) fun ...

  7. polyfll方案优化

    polyfill 在es6风靡的时候,babel给了我们一个有力的转换方案,可以在低版本浏览器上写一些新语法而不用考虑兼容问题 polyfill的诞生 语法和API区分 语法是用来产生特殊效果的一些符 ...

  8. 小米笔记本pro 黑苹果系统无法进入系统,频繁重启故障解决记录

    问题1:频繁重启,然后clover丢失 表现情况:开机没有选择macos 或windos的界面 解决办法:进入windows使用工具easyefi,直接添加一个clover start boot,选择 ...

  9. WIN7快速打开hosts方法

    WIN7快速打开hosts方法 1直接运行C:\Windows\System32\drivers\etc\hosts 浏览选择notepad++打开即可 2打开notepad++打开 C:\Windo ...

  10. Anaconda--在虚拟环境中安装CUDA and cudnn

    在conda虚拟环境中安装CUDAconda install cudatoolkit=8.0 -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs ...