VC windows api 多线程---临界区

临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。

  临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。

  在所有的同步对象中,临界区是最容易使用的,但是,一个临界区对一个进程或DLL是有限的,不能被其他进程共享,只能用于同步单个进程中的线程。临界区不是Windows内核对象,它和内核对象不同,存在于进程的内存空间中。
Win32 API提供了几个临界区函数:
  void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
  void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
  void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
  void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
CRITICAL_SECTION类型的变量用来扮演红绿灯的角色,让同一个时间内只有一个线程进入临界区。该临界区变量的声明必须是全局的,这样不同的线程就能访问它。操纵临界区的Win32函数初始化和维护该结构中的所有成员,不要自己去访问和修改任何成员。
使用临界区之前,必须调用InitializeCriticalSection()函数来初始化临界区。而通过调用EnterCriticalSection()函数来取得一个临界区的所有权。然后通过LeaveCriticalSection()函数来释放所有权。临界区通过一个线程取得所有权来显示它已经进入代码临界区的方法进行工作,如果其他线程调用EnterCriticalSection()并引用同一临界区,它会被阻塞,直到第一个线程调用LeaveCriticalSection()函数。最后,可以调用DeleteCriticalSection()函数来释放用户初始化临界区时分配的系统资源。

下面例1,例2用来帮助理解:

例1:

  #include "stdafx.h"
#include <stdio.h>
#include <windows.h>
#include <string.h>
#include <stdlib.h>
#define threadnum 10
typedef struct THREADDATA
{
int id;
char name[];
int sleep;
}THREADDATA;
CRITICAL_SECTION sec1;
char * str;
DWORD WINAPI ThreadProc( LPVOID lpParam )
{
THREADDATA *data=(THREADDATA *)lpParam;
printf("%d\n%s\n",data->id,data->name);
EnterCriticalSection(&sec1);
for(int i=;i<;i++)
{
// EnterCriticalSection(&sec1);
printf("thread%d:%d\n",data->id,i);
// LeaveCriticalSection(&sec1);
Sleep(data->sleep);
}
LeaveCriticalSection(&sec1);
return ;
}
int main(int argc, char* argv[])
{
str=(char*)malloc();
THREADDATA pData[threadnum];
DWORD dwThreadId[threadnum];
HANDLE hThread[threadnum];
InitializeCriticalSection(&sec1); for(int i=;i<threadnum;i++)
{
pData[i].id=i;
sprintf(pData[i].name,"yuguoqing");
pData[i].sleep=i*;
hThread[i] = CreateThread(NULL,,ThreadProc, pData+i, , dwThreadId+i);
}
WaitForMultipleObjects(threadnum, hThread, TRUE, INFINITE);
return ;
}

例2:

图1 使用临界区保持线程同步

  下面通过一段代码展示了临界区在保护多线程访问的共享资源中的作用。通过两个线程来分别对全局变量g_cArray[10]进行写入操作,用临界区结构对象g_cs来保持线程的同步,并在开启线程前对其进行初始化。为了使实验效果更加明显,体现出临界区的作用,在线程函数对共享资源g_cArray[10]的写入时,以Sleep()函数延迟1毫秒,使其他线程同其抢占CPU的可能性增大。如果不使用临界区对其进行保护,则共享资源数据将被破坏(参见图1(a)所示计算结果),而使用临界区对线程保持同步后则可以得到正确的结果(参见图1(b)所示计算结果)。代码实现清单附下:

  // 临界区结构对象
CRITICAL_SECTION g_cs;
// 共享资源
char g_cArray[];
UINT ThreadProc10(LPVOID pParam)
{
 // 进入临界区
 EnterCriticalSection(&g_cs);
 // 对共享资源进行写入操作
 for (int i = ; i < ; i++)
 {
  g_cArray[i] = 'a';
  Sleep();
 }
 // 离开临界区
 LeaveCriticalSection(&g_cs);
 return ;
}
UINT ThreadProc11(LPVOID pParam)
{
 // 进入临界区
 EnterCriticalSection(&g_cs);
 // 对共享资源进行写入操作
 for (int i = ; i < ; i++)
 {
  g_cArray[ - i - ] = 'b';
  Sleep();
 }
 // 离开临界区
 LeaveCriticalSection(&g_cs);
 return ;
}
……
void CSample08View::OnCriticalSection()
{
 // 初始化临界区
 InitializeCriticalSection(&g_cs);
 // 启动线程
 AfxBeginThread(ThreadProc10, NULL);
 AfxBeginThread(ThreadProc11, NULL);
 // 等待计算完毕
 Sleep();
 // 报告计算结果
 CString sResult = CString(g_cArray);
 AfxMessageBox(sResult);
}

MFC临界区

  在MFC中封装了CCriticalSection类作为临界区对象,在构造函数中自动调用InitialCriticalSection()函数,在析构函数中自动调用LeaveCriticalSection()函数,用Lock()和Unlock()对应取得所有权和释放所有权。

  // MFC临界区类对象
CCriticalSection g_clsCriticalSection;
// 共享资源
char g_cArray[];
UINT ThreadProc20(LPVOID pParam)
{
 // 进入临界区
 g_clsCriticalSection.Lock();
 // 对共享资源进行写入操作
 for (int i = ; i < ; i++)
 {
  g_cArray[i] = 'a';
  Sleep();
 }
 // 离开临界区
 g_clsCriticalSection.Unlock();
 return ;
}
UINT ThreadProc21(LPVOID pParam)
{
 // 进入临界区
 g_clsCriticalSection.Lock();
 // 对共享资源进行写入操作
 for (int i = ; i < ; i++)
 {
  g_cArray[ - i - ] = 'b';
  Sleep();
 }
 // 离开临界区
 g_clsCriticalSection.Unlock();
 return ;
}
……
void CSample08View::OnCriticalSectionMfc()
{
 // 启动线程
 AfxBeginThread(ThreadProc20, NULL);
 AfxBeginThread(ThreadProc21, NULL);
 // 等待计算完毕
 Sleep();
 // 报告计算结果
 CString sResult = CString(g_cArray);
 AfxMessageBox(sResult);
}

CPP-基础:临界区的更多相关文章

  1. CPP基础

    CPP基础1. 如果没有指明访问限定符(public,private),class中默认的private,而struct中的成员默认是public的. #include <iostream> ...

  2. 个人学习记录-Cpp基础-成员初始化列表

    Translator     Translator     参考链接: https://blog.csdn.net/XIONGXING_xx/article/details/115553291http ...

  3. win7 32 bit VS2012 OpenCV3.0配置

    今天看CPP基础,想起来之前在vs2012配置opencv3未成功,就忍不住再次配置一... 环境:win7 32bit vs2012 opencv3.0 主要参考这几篇博文:1,2,3 上面的博文已 ...

  4. 20160226.CCPP体系详解(0036天)

    程序片段(01):01.多线程.c+02.多线程操作.c 内容概要:多线程 ///01.多线程.c #include <stdio.h> #include <stdlib.h> ...

  5. 在vs2013下手把手创建/调用dll

    body { font: 16px } 参考了大佬的文章 首先,体会一下静态编译: 创建Win32Project,选DLL,添加一个.h和.cpp文件 点击生成解决方案,然后去debug目录下拷贝.l ...

  6. 双目相机标定以及立体测距原理及OpenCV实现

    单目相机标定的目标是获取相机的内参和外参,内参(1/dx,1/dy,Cx,Cy,f)表征了相机的内部结构参数,外参是相机的旋转矩阵R和平移向量t.内参中dx和dy是相机单个感光单元芯片的长度和宽度,是 ...

  7. 头部姿态估计 - OpenCV/Dlib/Ceres

    基本思想 通过Dlib获得当前人脸的特征点,然后通过旋转平移标准模型的特征点进行拟合,计算标准模型求得的特征点与Dlib获得的特征点之间的差,使用Ceres不断迭代优化,最终得到最佳的旋转和平移参数. ...

  8. QT_study

    https://blog.csdn.net/a313827758/article/details/72736552 https://blog.csdn.net/xbcreal/article/deta ...

  9. 零基础逆向工程36_Win32_10_互斥体_互斥体与临界区的区别

    1 引言 讲了第二个内核对象,互斥体.前面已经学过一个内核对象,线程.这节讲两个函数,WaitForSingleObject()和WaitForMultipleObjects().因此这两个函数是根据 ...

  10. 零基础逆向工程35_Win32_09_临界区_CRITICAL_SECTION结构

    1 引入 为什么会存在临界区这中机制呢?是为多线程同时访问全局变量而引入的.也就是上一篇帖子的末尾流出的问题程序的解决办法. 看懂了上面的,那么我们再罗嗦总结一下: 1.多线程访问全局变量时,存在线程 ...

随机推荐

  1. Java - 让1+1的结果变成3

    原出处是国外某论坛某帖子中楼主提问:如何让1+1=3?于是出现了各种语言实现的各种机制的答案,当然其中也包括直接用字符串输出"1+1=3"...最后被采纳的是用Java语言实现的答 ...

  2. linux系统下部署项目

    一.修改防火墙设置,开放对应的端口 修改Linux系统防火墙配置需要修改 /etc/sysconfig/iptables 这个文件,如果要开放哪个端口,在里面添加一条  -A RH-Firewall- ...

  3. java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List com.yunweather.app.db.YunWeatherDB.loadProvinces()' on a null object reference

    NullPointerException:查看自己的什么地方是否对空指针进行了操作 Attempt to invoke virtual method 'java.util.List com.yunwe ...

  4. cookie实现记住登录名和密码

    在最近学习的session作用域中,顺便了解了一下cookie, session是存放在服务器中,而cookie是存放在客户端中. 本篇文章主要是使用cookie来实现记住密码的功能. 简单的logi ...

  5. BestCoder Round #92

    这里是逢比赛必挂的智障选手ysf…… 不知道是因为自己菜还是心态不好……也许是后者吧,毕竟每次打比赛的时候都会很着急.lrd说我打比赛的功利性太强,想想确实是这样. 昨天打完之后自觉身败名裂没敢写出来 ...

  6. 基础架构之Redis

    项目开发过程中,有些信息的变动频率是很低但又经常访问到,这些信息我们往往放在缓存中,目前在缓存组件中,Redis绝对值得你列入使用计划.更多详细信息可以参考官网 https://redis.io/.这 ...

  7. Docker常用操作指令

    1.查看正在运行的容器 docker ps 2.查看所有容器 docker ps -a 3.停用所有正在运行的容器 docker stop $(docker ps -q) 4.删除所有容器 docke ...

  8. Android 友盟统计的集成与使用(包含多渠道打包配置)

    前言 app上线后,一般公司都希望跟踪app在市场上的使用情况.包括新增用户.活跃用户.渠道信息.错误信息等,还有例如商城类的app,需要跟踪用户最喜欢浏览哪种类型的店铺或商品.这些都可以通过集成友盟 ...

  9. c 读取整个文件内容

    char* textFileRead(char* filename){char* text;FILE *pf = fopen(filename,"r");fseek(pf,0,SE ...

  10. Java—IO流 字符流

    java的文本(char)是16位无符号整数,是字符的unicode编码(双字节编码). 文件是byte byte byte ... 的数据序列. 文本文件是文本(char)序列按照某种编码方案(uf ...