一、前言

  最近,在项目中涉及到多线程访问临界资源的问题。为了保护临界资源,可以是使用互斥量或者是使用临界区。由于,我不需要在多进程中同步,又为了效率的考量,所以选择了使用临界区的方式。但是,在使用临界区的时候,发现了一个类是鸡生蛋蛋生鸡的问题。现将问题和自己的解决方法记录如下,如有不对之处,还请指教。

二、出现的问题

  在项目的开发过程中,需要把视屏流输出成磁盘的文件,有时候可能有多路视频流同时需要输出到各自的文件中去。对于不同路的视频需要进行分类,不同路的视频存储在不同的目录下。于是在初始化输出文件的时候,需要设置当前目录。有多路视频的时候,每一个都需要在初始化的时候设置当前目录,于是设置的当前目录就是临界资源了。

  下面,是我的输出视频流文件的类:

 class CAVIFile
{
public:
CAVIFile(); HRESULT OpenFile(const string& strFileName, const string& strDirectoryPath)
{
// ....
SetCurrentDirectory(strDirectoryPath.c_str());
// ...
} HRESULT WriteVideo(LPVOID lpBuffer, LONG cbBuffer); // 写入视频数据
HRESULT WriteAudio(LPVOID lpBuffer, LONG cbBuffer); // 写入获取到的音频数据
void CloseFile(); private:
// 一些与本内容无关的成员变量
// ...
};

  可以看出,如果多个线程同时,进行多路视频的输出时,都调用OpenFile()初始化输出文件,当第一个线程设置当前目录,还没有初始化完成的时候,第二个线程也调用了OpenFile()设置了不同的当前目录,那么第一个线程的初始化的当前目录就会被覆盖。此时就需要加锁,这里选择了临界区。由于不同CAVIFile对象由不同的线程执行,而不同的CAVIFile对象设置当前目录,都会相互影响,所以不同的CAVIFile对象需要公用一个临界区。所以声明临界区为静态成员。

  下面是加上临界区之后的代码:

 class CAVIFile
{
public:
CAVIFile()
{
InitializeCriticalSection(&m_criticalSection); // 初始化临界区
}
HRESULT OpenFile(const string& strFileName, const string& strDirectoryPath)
{
// ....
EnterCriticalSection(&m_criticalSection);
SetCurrentDirectory(strDirectoryPath.c_str());
// ...
LeaveCriticalSection(&m_criticalSection);
//...
}
// ...
private:
// ...
// 整个类设置当前目录的互斥锁
static CRITICAL_SECTION m_criticalSection;
};
CRITICAL_SECTION CAVIFile::m_criticalSection;

  但是上面的代码还是有问题,那么就是每生成一个CAVIFile对象,就会InitializeCriticalSection(&m_criticalSection);一次,这是有问题的,m_criticalSection是静态成员变量,这个临界区是CAVIFile类对象共享的,每个m_criticalSection应该只需要InitializeCriticalSection(&m_criticalSection)一次,而这里会InitializeCriticalSection()多次,这样可能导致资源的泄漏(对一个CRITICAL_SECTION对象InitializeCriticalSection()多次,会出现什么样的问题,我自己没有深入研究过,这里只是猜测!!但是CRITICAL_SECTION对象正常只需要InitializeCriticalSection()操作一次)。

1.加静态变量标识是否需要InitializeCriticalSection()

  对此这个InitializeCriticalSection(&m_criticalSection);多次的问题,我看到的一种解决方法是,加一个静态变量去标识m_criticalSection所代表的临界区是否已经InitializeCriticalSection(),如果已经InitializeCriticalSection()就不再InitializeCriticalSection()。

  代码如下:

 class CAVIFile
{
public:
CAVIFile()
{
6 if (m_shouldInit)
7 {
8 InitializeCriticalSection(&m_criticalSection); // 初始化临界区
9 m_shouldInit = false;
10 }
}
HRESULT OpenFile(const string& strFileName, const string& strDirectoryPath)
{
// ....
EnterCriticalSection(&m_criticalSection);
SetCurrentDirectory(strDirectoryPath.c_str());
// ...
LeaveCriticalSection(&m_criticalSection);
//...
}
// ...
private:
// ...
// 整个类设置当前目录的互斥锁
static CRITICAL_SECTION m_criticalSection;
static bool m_shouldInit;
};
CRITICAL_SECTION CAVIFile::m_criticalSection;
bool CAVIFile::m_shouldInit = true;

  这其实还是有问题的。在上面的代码中多线程可能同时访问m_shouldInit,判断是否需要InitializeCriticalSection()临界区,如果已经InitializeCriticalSection(),就不再InitializeCriticalSection()初始化临界区,这看起来是正确一样。但是仅仅是看起来。此时,当多线程访问m_shouldInit的时候,m_shouldInit也变成了临界资源了。为了保护m_shouldInit,难道我们再定义一个临界区?那么我们再次定义的临界区的只能InitializeCriticalSection(&m_criticalSection)一次的问题又出现了,我们再定义另一个静态变成去标识我们刚刚为了保护m_shouldInit定义的临界区的只一次InitializeCriticalSection()问题?此时,我们已经落入到蛋生鸡,鸡生蛋的逻辑中了。

  明显这种解决方法是不正确的!

2、使用单例模式

  在单例模式中,一个使用单例模式的类,只能创建一个类的对象。我突然想起,这个类只能创建一个对象,那么也就是只会调用一次构造函数。那么,我可以利用单例模式进行InitializeCriticalSection()这个临界区。

  实现代码如下:

 class CAVIFile
{
public:
// ...
HRESULT OpenFile(const string& strFileName, const string& strDirectoryPath)
{
// ....
EnterCriticalSection(&m_criticalSection);
SetCurrentDirectory(strDirectoryPath.c_str());
// ...
LeaveCriticalSection(&m_criticalSection);
//...
}
// ...
private:
//...
// 整个类设置当前目录的互斥锁
static CRITICAL_SECTION m_criticalSection;
private:
// Singleton,这个类使用单例模式,为了只初始化一次m_criticalSection
// 饿汉式
22 class Singleton
23 {
24 private:
25 Singleton() { InitializeCriticalSection(&m_criticalSection); }
26 Singleton(const Singleton& other);
27 Singleton& operator=(const Singleton& other);
28
29 static Singleton m_Singleton;
30 };
31 friend class Singleton;
};
CRITICAL_SECTION CAVIFile::m_criticalSection;
CAVIFile::Singleton CAVIFile::Singleton::m_Singleton;

  这里,在类内定义了一个嵌套类,而嵌套类使用了饿汉式的单例模式,自动只会构造一次,那么就只会对m_criticalSection ,InitializeCriticalSection()一次。这样就完美的解决了问题。

用单例模式解决临界区(CRITICAL_SECTION)的使用问题的更多相关文章

  1. python的单例模式--解决多线程的单例模式失效

    单例模式 单例模式(Singleton Pattern) 是一种常用的软件设计模式,主要目的是确保某一个类只有一个实例存在.希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场 比如,某个 ...

  2. 单例模式解决RabbitMQ超出最大连接问题

    今天在项目稳定性测试过程中遇到一个情景:通过工具jMeter一直请求消息转发服务器,消息转发服务器再向rabbitMQ发送数据,在这期间出现了问题.MQ意外宕机. 1. 查看rabbitMQ管理界面. ...

  3. js设计模式总结-单例模式

    单例模式 解决的问题 保证实例只有一个,避免多个实现,从全局来看,这个实例的状态是唯一的. 实现原理 设置一个变量来记录实例,通过检测该变量是否为空来决定是否创建实例 非透明单例 所谓非透明就是用户在 ...

  4. iOS中常见的设计模式——单例模式\委托模式\观察者模式\MVC模式

    一.单例模式 1. 什么是单例模式? 在iOS应用的生命周期中,某个类只有一个实例. 2. 单例模式解决了什么问题? 想象一下,如果我们要读取文件配置信息,那么每次要读取,我们就要创建一个文件实例,然 ...

  5. PHP模式设计之单例模式、工厂模式、注册树模式、适配器模式、观察者模式

    php模式设计之单例模式 什么是单例模式? 单例模式是指在整个应用中只有一个实例对象的设计模式 为什么要用单例模式? php经常要链接数据库,如果在一个项目中频繁建立连接数据库,会造成服务器资源的很大 ...

  6. 解决logging模块日志信息重复问题

    解决logging模块日志信息重复问题 问题描述 相信大家都知道python的logging模块记录日志信息的步骤: # coding:utf-8 import logging ### 创建logge ...

  7. windows多线程同步--临界区

    推荐参考博客:秒杀多线程第五篇 经典线程同步 关键段CS   关于临界区的观念,一般操作系统书上面都有. 适用范围:它只能同步一个进程中的线程,不能跨进程同步.一般用它来做单个进程内的代码快同步,效率 ...

  8. PHP设计模式-工厂模式、单例模式、注册模式

    本文参考慕课网<大话PHP设计模式>-第五章内容编写,视频路径为:http://www.imooc.com/video/4876 推荐阅读我之前的文章:php的设计模式 三种基本设计模式, ...

  9. java 单例模式之线程安全的饿汉模式和懒汉模式

    转载博主:thankyou https://blog.csdn.net/twj13162380953/article/details/53869983 理解: 饿汉式获取实例的步骤简单所以线程更安全. ...

随机推荐

  1. VS2013安装及破解教程

    https://blog.csdn.net/qq_33742119/article/details/80075352 软件下载的百度云链接,也可以在官网直接下载 链接:https://pan.baid ...

  2. 流水的算法,铁打的损失函数/MLE

    机器学习算法可以说是不少的,如果死记硬背的话,只能当时记得推导过程和步骤,过一段时间就又想不起来了,只能依稀记得一些影子.所以,应该找到算法的一些通用的方法来理解算法的思路以及推导过程. 我认为,最大 ...

  3. SDUT OJ 数据结构实验之串二:字符串匹配

    数据结构实验之串二:字符串匹配 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem Descrip ...

  4. redis incr自增指定的无限期 key 删除问题

    不得不说,最近在使用redis的incr的功能的时候,着实是踩了不少的坑啊! 前面我写的一篇文章里面,我提到通过incr产生的键在用普通的get的方式去获取的时候,是获取不到的,所以我不得不通过特殊的 ...

  5. 接口登录存在动态token

    Jmeter接口登录时获取到的参数token一直在变的问题,导致运行时总是报错 解决方法如下: 1.新建一个GET的HTTP请求 2.添加正则表达式提取器 记得name="_token&qu ...

  6. ORA-28009: 应当以 SYSDBA 身份或 SYSOPER 身份建立 SYS 连接

    用 SQL*Plus 连接数据库的时候,除了用户名和密码外,还要在口令后面加一个主机字符串.如下: 请输入用户名:sys 口令:ANKoracle123,orcl as sysdba

  7. POJ_1019 Number Sequence 【递推】

    题目: A single positive integer i is given. Write a program to find the digit located in the position ...

  8. AtCoder Beginner Contest 113 B

    B - Palace Time limit : 2sec / Memory limit : 1024MB Score: 200 points Problem Statement A country d ...

  9. 【VBA研究】Excel VBA利用ADODB访问数据库使用小结

    [转自] http://blog.csdn.net/iamlaosong/article/details/18043433 作者:iamlaosong ▲连接Oracle数据库 Set cnn = C ...

  10. python 函数的递归

    递归:简单来说就是自己调用自己 这里我们又要举个例子来说明递归能做的事情. 例一: 现在你们问我,alex老师多大了?我说我不告诉你,但alex比 egon 大两岁. 你想知道alex多大,你是不是还 ...