欢迎光临
梦想从学习开始!

用单例模式解决临界区(CRITICAL_SECTION)的使用问题| 小熊测试

本文主要介绍 用单例模式解决临界区(CRITICAL_SECTION)的使用问题| 小熊测试,小熊希望对大家的学习或者工作具有一定的参考学习价值,在测试领域有所提升和发展。

  一、前言

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

  二、出现的问题

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

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

1 class CAVIFile

2 {

3 public:

4     CAVIFile();

5

6     HRESULT OpenFile(const string& strFileName, const string& strDirectoryPath)

7     {

8         // ….

9         SetCurrentDirectory(strDirectoryPath.c_str());

10         // …

11     }

12

13     HRESULT WriteVideo(LPVOID lpBuffer, LONG cbBuffer); // 写入视频数据

14     HRESULT WriteAudio(LPVOID lpBuffer, LONG cbBuffer); // 写入获取到的音频数据

15     void CloseFile();

16

17 private:

18     // 一些与本内容无关的成员变量

19     // …

20 };

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

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

1 class CAVIFile

2 {

3 public:

4     CAVIFile()

5     {

6         InitializeCriticalSection(&m_criticalSection); // 初始化临界区

7     }

8     HRESULT OpenFile(const string& strFileName, const string& strDirectoryPath)

9     {

10         // ….

11         EnterCriticalSection(&m_criticalSection);

12         SetCurrentDirectory(strDirectoryPath.c_str());

13         // …

14         LeaveCriticalSection(&m_criticalSection);

15         //…

16     }

17     // …

18 private:

19     // …

20     // 整个类设置当前目录的互斥锁

21     static CRITICAL_SECTION m_criticalSection;

22 };

23 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()。

  代码如下:

1 class CAVIFile

2 {

3 public:

4     CAVIFile()

5     {

6         if (m_shouldInit)

7         {

8             InitializeCriticalSection(&m_criticalSection); // 初始化临界区

9             m_shouldInit = false;

10         }

11     }

12     HRESULT OpenFile(const string& strFileName, const string& strDirectoryPath)

13     {

14         // ….

15         EnterCriticalSection(&m_criticalSection);

16         SetCurrentDirectory(strDirectoryPath.c_str());

17         // …

18         LeaveCriticalSection(&m_criticalSection);

19         //…

20     }

21     // …

22 private:

23     // …

24     // 整个类设置当前目录的互斥锁

25     static CRITICAL_SECTION m_criticalSection;

26     static bool m_shouldInit;

27 };

28 CRITICAL_SECTION CAVIFile::m_criticalSection;

29 bool  CAVIFile::m_shouldInit = true;

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

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

  2、使用单例模式

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

  实现代码如下:

1 class CAVIFile

2 {

3 public:

4     // …

5     HRESULT OpenFile(const string& strFileName, const string& strDirectoryPath)

6     {

7         // ….

8         EnterCriticalSection(&m_criticalSection);

9         SetCurrentDirectory(strDirectoryPath.c_str());

10         // …

11         LeaveCriticalSection(&m_criticalSection);

12         //…

13     }

14     // …

15 private:

16     //…

17     // 整个类设置当前目录的互斥锁

18     static CRITICAL_SECTION m_criticalSection;

19 private:

20     // Singleton,这个类使用单例模式,为了只初始化一次m_criticalSection

21     // 饿汉式

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;

32 };

33 CRITICAL_SECTION CAVIFile::m_criticalSection;

34 CAVIFile::Singleton CAVIFile::Singleton::m_Singleton;

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小熊分享邦(www.xxfxb.com),希望大家能坚持软件测试之路,谢谢。

赞(0) 打赏
未经允许不得转载:小熊分享邦 » 用单例模式解决临界区(CRITICAL_SECTION)的使用问题| 小熊测试

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏