用單例模式解決臨界區(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()一次。這樣就完美的解決了問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM