用单例模式解决临界区(CRITICAL_SECTION)的使用问题
一、前言
最近,在项目中涉及到多线程访问临界资源的问题。为了保护临界资源,可以是使用互斥量或者是使用临界区。由于,我不需要在多进程中同步,又为了效率的考量,所以选择了使用临界区的方式。但是,在使用临界区的时候,发现了一个类是鸡生蛋蛋生鸡的问题。现将问题和自己的解决方法记录如下,如有不对之处,还请指教。
二、出现的问题
在项目的开发过程中,需要把视屏流输出成磁盘的文件,有时候可能有多路视频流同时需要输出到各自的文件中去。对于不同路的视频需要进行分类,不同路的视频存储在不同的目录下。于是在初始化输出文件的时候,需要设置当前目录。有多路视频的时候,每一个都需要在初始化的时候设置当前目录,于是设置的当前目录就是临界资源了。
下面,是我的输出视频流文件的类:
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)的使用问题的更多相关文章
- python的单例模式--解决多线程的单例模式失效
单例模式 单例模式(Singleton Pattern) 是一种常用的软件设计模式,主要目的是确保某一个类只有一个实例存在.希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场 比如,某个 ...
- 单例模式解决RabbitMQ超出最大连接问题
今天在项目稳定性测试过程中遇到一个情景:通过工具jMeter一直请求消息转发服务器,消息转发服务器再向rabbitMQ发送数据,在这期间出现了问题.MQ意外宕机. 1. 查看rabbitMQ管理界面. ...
- js设计模式总结-单例模式
单例模式 解决的问题 保证实例只有一个,避免多个实现,从全局来看,这个实例的状态是唯一的. 实现原理 设置一个变量来记录实例,通过检测该变量是否为空来决定是否创建实例 非透明单例 所谓非透明就是用户在 ...
- iOS中常见的设计模式——单例模式\委托模式\观察者模式\MVC模式
一.单例模式 1. 什么是单例模式? 在iOS应用的生命周期中,某个类只有一个实例. 2. 单例模式解决了什么问题? 想象一下,如果我们要读取文件配置信息,那么每次要读取,我们就要创建一个文件实例,然 ...
- PHP模式设计之单例模式、工厂模式、注册树模式、适配器模式、观察者模式
php模式设计之单例模式 什么是单例模式? 单例模式是指在整个应用中只有一个实例对象的设计模式 为什么要用单例模式? php经常要链接数据库,如果在一个项目中频繁建立连接数据库,会造成服务器资源的很大 ...
- 解决logging模块日志信息重复问题
解决logging模块日志信息重复问题 问题描述 相信大家都知道python的logging模块记录日志信息的步骤: # coding:utf-8 import logging ### 创建logge ...
- windows多线程同步--临界区
推荐参考博客:秒杀多线程第五篇 经典线程同步 关键段CS 关于临界区的观念,一般操作系统书上面都有. 适用范围:它只能同步一个进程中的线程,不能跨进程同步.一般用它来做单个进程内的代码快同步,效率 ...
- PHP设计模式-工厂模式、单例模式、注册模式
本文参考慕课网<大话PHP设计模式>-第五章内容编写,视频路径为:http://www.imooc.com/video/4876 推荐阅读我之前的文章:php的设计模式 三种基本设计模式, ...
- java 单例模式之线程安全的饿汉模式和懒汉模式
转载博主:thankyou https://blog.csdn.net/twj13162380953/article/details/53869983 理解: 饿汉式获取实例的步骤简单所以线程更安全. ...
随机推荐
- 洛谷P2775 机器人路径规划问题
传送门 题解 至今没看懂这深搜怎么写的…… //minamoto #include<iostream> #include<cstdio> #include<cstring ...
- node创建一个简单的web服务
本文将如何用node创建一个简单的web服务,过程也很简单呢~ 开始之前要先安装node.js 1.创建一个最简单的服务 // server.js const http = require('http ...
- Zookeeper客户端对比选择_4
Zookeeper客户端对比选择 本文思维导图 使用框架的好处是自带一套实用的API,但是Zookeeper虽然非常强大,但是社区却安静的可怕,版本更新较慢,下面会先从zookeeper原生API的不 ...
- 50个php程序性能优化的方法,赶紧收藏吧!
1. 用单引号代替双引号来包含字符串,这样做会更快一些.因为 PHP 会在双引号包围的 字符串中搜寻变量,单引号则不会,注意:只有 echo 能这么做,它是一种可以把多个字符 串当作参数的“函数”(译 ...
- HTML5 indexedDb 数据库
indexedDb 数据库 上一节中,我们知道了,HMTL5中内置了两种本地数据库,一种是通过SQL语言来访问的文件型SQL数据库被称为“SQLLite,另一种是是被称为indexedDB 的数据 ...
- AndroidManifest.xml警告
新建一个android项目后,AndroidManifest.xml有一个黄色警告 作为一个新手,不知道这个警告来自哪里,点击界面下方的不同标签,才知道来自图中的位置 第8行中,application ...
- flask_restful
from flask_restful import (Resource, reqparse) # 参数解析对象生成 parser = reqparse.RequestParser() parser.a ...
- PHP报错
php.ini ; 错误日志 log_errors = On ; 显示错误 display_errors = Off ; 日志路径 error_log = "/usr/local/lnmp/ ...
- python 网页爬取数据生成文字云图
1. 需要的三个包: from wordcloud import WordCloud #词云库 import matplotlib.pyplot as plt #数学绘图库 import jieba; ...
- Activemq API使用(整合spring)
整合spring之后,主要用的就是org.springframework.jms.core.JmsTemplate的API了,在spring-jms-xxx.jar中. 引入整合需要的jar包: &l ...