多线程(四)多线程同步_Critical Section临界区

临界区是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权。和使用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. 先定义相关全局变量和线程函数前置声明

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

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

5.OnInitDialog中添加相应代码

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

OnInitDialog

6.按钮_启动线程事件

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

按钮_启动线程事件

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

 1 //线程_售票窗口1
 2 DWORD WINAPI ThreadSellTicket_One(LPVOID lpParam)
 3 {
 4     CString strEdit;
 5     CString strNew;
 6     while (true)
 7     {
 8         Sleep(1000);
 9         EnterCriticalSection(&g_cs);
10         g_editShowInfo->GetWindowText(strEdit);
11         if(g_nTickNum > 0)
12         {
13             strNew.Format(_T("线程1:剩余票数:%d,售出1张票\r\n"),g_nTickNum--);
14             strEdit += strNew;
15             if(g_nTickNum == 0)
16             {
17                 strEdit += _T("线程1:票己售空,关闭售票窗口\r\n");
18                 g_editShowInfo->SetWindowText(strEdit);
19                 LeaveCriticalSection(&g_cs);
20                 break;
21             }
22             else
23             {
24                 g_editShowInfo->SetWindowText(strEdit);
25                 LeaveCriticalSection(&g_cs);
26             }
27         }
28         else
29         {
30             strEdit += _T("线程1:票己售空,关闭售票窗口\r\n");
31             g_editShowInfo->SetWindowTextW(strEdit);
32             LeaveCriticalSection(&g_cs);
33             break;
34         }
35     }
36     return true;
37 }

线程_售票窗口1

 1 DWORD WINAPI ThreadSellTicket_Two(LPVOID lpParam)
 2 {
 3     CString strEdit;
 4     CString strNew;
 5     while (true)
 6     {
 7         Sleep(1000);
 8         EnterCriticalSection(&g_cs);
 9         g_editShowInfo->GetWindowText(strEdit);
10         if(g_nTickNum > 0)
11         {
12             strNew.Format(_T("线程2:剩余票数:%d,售出1张票\r\n"),g_nTickNum--);
13             strEdit += strNew;
14             if(g_nTickNum == 0)
15             {
16                 strEdit += _T("线程2:票己售空,关闭售票窗口\r\n");
17                 g_editShowInfo->SetWindowText(strEdit);
18                 LeaveCriticalSection(&g_cs);
19                 break;
20             }
21             else
22             {
23                 g_editShowInfo->SetWindowText(strEdit);
24                 LeaveCriticalSection(&g_cs);
25             }
26         }
27         else
28         {
29             strEdit += _T("线程2:票己售空,关闭售票窗口\r\n");
30             g_editShowInfo->SetWindowTextW(strEdit);
31             LeaveCriticalSection(&g_cs);
32             break;
33         }
34     }
35     return true;
36 }

线程_售票窗口2

 1 DWORD WINAPI ThreadSellTicket_Three(LPVOID lpParam)
 2 {
 3     CString strEdit;
 4     CString strNew;
 5     while (true)
 6     {
 7         Sleep(1000);
 8         EnterCriticalSection(&g_cs);
 9         g_editShowInfo->GetWindowText(strEdit);
10         if(g_nTickNum > 0)
11         {
12             strNew.Format(_T("线程3:剩余票数:%d,售出1张票\r\n"),g_nTickNum--);
13             strEdit += strNew;
14             if(g_nTickNum == 0)
15             {
16                 strEdit += _T("线程3:票己售空,关闭售票窗口\r\n");
17                 g_editShowInfo->SetWindowText(strEdit);
18                 LeaveCriticalSection(&g_cs);
19                 break;
20             }
21             else
22             {
23                 g_editShowInfo->SetWindowText(strEdit);
24                 LeaveCriticalSection(&g_cs);
25             }
26         }
27         else
28         {
29             strEdit += _T("线程3:票己售空,关闭售票窗口\r\n");
30             g_editShowInfo->SetWindowTextW(strEdit);
31             LeaveCriticalSection(&g_cs);
32             break;
33         }
34     }
35     return true;
36 }

线程_售票窗口3

8.DestroyWindow添加相应代码

1 BOOL CCriticalSectionDemoDlg::DestroyWindow()
2 {
3     //删除临界区
4     DeleteCriticalSection(&g_cs);
5     return CDialogEx::DestroyWindow();
6 }

DestroyWindow

最终演示效果如下:

原文地址:https://www.cnblogs.com/fzxiaoyi/p/12072551.html

时间: 2024-09-27 23:37:27

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

第七十四课、多线程间的同步

一.多线程间的同步 1.多线程编程的本质 (1).并发性是多线程编程的本质 (2).在宏观上,所有线程并行执行 (3).多个线程间相互独立,互不干涉 2.特殊情况下,多线程存在依赖 煮菜和煮饭这两个线程结束后,才能进行吃饭的线程 3.同步的概念 (1).在特殊情况下,控制多线程间的相对执行顺序 (2).QThread类支持线程间的同步 #include <QCoreApplication> #include <QThread> #include <QDebug> /*

mfc小工具开发之定时闹钟之---多线程急线程同步

一.MFC对多线程编程的支持 MFC中有两类线程,分别称之为工作者线程和用户界面线程.二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环. 工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等.用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等.但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务. 在MFC中,一般用全局函数Afx

JAVA多线程--线程的同步安全

每当我们在项目中使用多线程的时候,我们就不得不考虑线程的安全问题,而与线程安全直接挂钩的就是线程的同步问题.而在java的多线程中,用来保证多线程的同步安全性的主要有三种方法:同步代码块,同步方法和同步锁.下面就一起来看: 一.引言 最经典的线程问题:去银行存钱和取钱的问题,现在又甲乙两个人去同一个账户中取款,每人取出800,但是账户中一共有1000元,从逻辑上来讲,如果甲取走800,那么乙一定取不出来800: 1 package thread.threadInBank; 2 3 /** 4 *

Apache Kafka系列(四) 多线程Consumer方案

Apache Kafka系列(一) 起步 Apache Kafka系列(二) 命令行工具(CLI) Apache Kafka系列(三) Java API使用 Apache Kafka系列(四) 多线程Consumer方案 本文的图片是通过PPT截图出的,读者如果修改意见请联系我 一.Consumer为何需要实现多线程 假设我们正在开发一个消息通知模块,该模块允许用户订阅其他用户发送的通知/消息.该消息通知模块采用Apache Kafka,那么整个架构应该是消息的发布者通过Producer调用AP

C# 多线程之线程同步

多线程间应尽量避免同步问题,最好不要线程间共享数据.如果必须要共享数据,就需要使用同步技术,确保一次只有一个线程访问和改变共享状态. 一::lock语句 lock语句事设置锁定和接触锁定的一种简单方法.其语法非常简单: lock (obj) { // 需要发生同步的代码区 } 将共享数据的操作代码,放在上述的"{...}"区域内.锁定的对象(obj)必须是引用类型,如果锁定一个值类型,实际是锁定了它的一个副本,并没有实现锁定功能. 一般地,被锁定对象需要被创建为 私有 只读 引用类型:

Linux程序设计学习笔记----多线程编程线程同步机制之互斥量(锁)与读写锁

互斥锁通信机制 基本原理 互斥锁以排他方式防止共享数据被并发访问,互斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个互斥锁逻辑上绑定之后,对该资源的访问操作如下: (1)在访问该资源之前需要首先申请互斥锁,如果锁处于开状态,则申请得到锁并立即上锁(关),防止其他进程访问资源,如果锁处于关,则默认阻塞等待. (2)只有锁定该互斥锁的进程才能释放该互斥锁. 互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义. 互斥量

win32多线程 (四) Mutex

Mutex 用途和critical  section 非常类似,不过Mutex是内核对象,速度比section慢.Mutexes可以跨进程使用.另外Mutex在等待的时候可以设置等待时间. 以下是两种对象的相关函数比较: CRITICAL_SECTION Mutex 核心对象 --------------------------------------------------------------------------------------InitializeCriticalSection

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

Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享给大家. 一.对于线程同步和同步锁的理解(注:分享了三篇高质量的博客) 以下我精心的挑选了几篇博文,分别是关于对线程同步的理解和如何选择线程锁以及了解线程锁的作用范围. <一>线程同步锁的选择 1. 这里我推荐下Java代码质量改进之:同步对象的选择这篇博文. 2. 以上推荐的博文是以卖火车票为例

【2017-06-20】Linux应用开发工程师C/C++面试问题之一:Linux多线程程序的同步问题

参考之一:Linux 线程同步的三种方法 链接地址:http://www.cnblogs.com/eleclsc/p/5838790.html 简要回答: Linux下线程同步最常用的三种方法就是互斥锁.条件变量及信号量. 互斥锁通过锁机制来实现线程间的同步,锁机制是同一时刻只允许一个线程执行一个关键部分的代码. 条件变量是用来等待而不是用来上锁的,条用来自动阻塞一个线程,直到某特殊情况发生为止,通常条件变量和互斥锁同时使用. 线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变