如何建立一個簡單的C++同步鎖框架(譯)

翻譯自codeproject上面的一篇文章,題目是:如何建立一個簡單的c++同步鎖框架php

目錄

  1. 介紹html

  2. 背景c++

  3. 臨界區 & 互斥 & 信號git

    1. 臨界區github

    2. 互斥windows

    3. 信號安全

    4. 更多信息多線程

  4. 創建鎖框架的目的框架

  5. BaseLock函數

  6. 臨界區類

    1. 構造/拷貝構造

    2. 析構

    3. Lock/TryLock

    4. TryLockFor

    5. 解鎖

  7. 信號量類

    1. 構造/拷貝構造

    2. 析構

    3. Lock/TryLock/TryLockFor

    4. 解鎖

    5. 釋放

  8. 互斥類

    1. 構造/拷貝構造

    2. 析構

    3. Lock/ TryLock/TryLockFor

    4. 解鎖

  9. 無鎖類

    1. 實現

  10. 自動釋放鎖類

    1. 實現

  11. 例子

    1. 聲明同步對象

    2. 聲明帶初始計數的信號量對象

    3. 聲明跨進程的互斥/信號量對象

    4. 同步示例1 (手動加鎖)

    5. 同步示例2 (自動加鎖)

    6. 同步示例3 (TryLock/TryLockFor)

  12. 更多的示例

  13. 結論

  14. 參考

 

1 介紹

  開發多線程/多進程應用時, 同步是一個很大的問題. 你須要讓你的程序同時知足單線程和多線程/多進程的應用場合,固然如何管理同步又是另外一個話題了。利用鎖(同步)機制不會自動幫組你解決上訴問題,可是有助於你找到解決問題的方法。這篇文章向你解釋瞭如何構建簡單鎖框架,如何在你的應用中運用它。可能不少人已經開發了本身的鎖框架,或者利用成熟的庫好比 boost library或者 Intel TBB。可是有時候它們對於你的應用來講可能過於臃腫,也可能並非你想要的。經過閱讀本文,你可能構建符合你本身的鎖框架。

本文將解釋下面這些東西:

  • BaseLock

  • Critical Section

  • Semaphore

  • Mutex

  • NoLock

  • Auto-release Lock

  • 示例

  提示: 本文既不是要講解高級的鎖框架,也不是要講解同步基元的區別。正如題目所說,本文只會告訴你:如何構建一個簡單的鎖框架,你能夠根據本文的框架結合你本身的須要,運用或者修改它


2 背景

這裏你必須瞭解如下幾個同步基元的概念和他們之間的區別:

  • Critical Section

  • Semaphore

  • Mutex

若是你對MFC中的同步類(CSingleLock/CMultiLock, CCriticalSection, CSemaphore, and CMutex)有所瞭解,這將有助於你理解並運用這些類來代替windows函數庫去實現一個鎖框架。

 

3 Critical Section & Mutex & Semaphore

下面這段主要來自於這篇文章 "Synchronization in Multithreaded Applications with MFC" ,由Arman S所寫,我只是引用其中的內容,全部權爲他本人全部。

  • Critical Section

臨界區工做於用戶模式,若是須要能夠進入內核模式。若是一個線程運行一段位於臨界區的代碼,它首先會產生一個自旋區並在一段時間的等待以後進入內核模式並等待臨界區對象。實際上臨界區由自旋計數和信號量組成。前者用於用戶態模式下的等待,後者用於內核模式下的等待(睡眠)。在win32 API中,結構體CRITICAL_SECTION表示了一個臨界區。在MFC中,用類CCriticalSection表示。概念上一個臨界區表示一段可運行的代碼,並保證這段代碼在執行過程當中不會被中斷打斷。這樣作是爲了保證一個單線程在訪問共享資源時,擁有絕對佔有權。

  • Mutex

互斥與臨界區同樣,也是用於同步訪問時保護共享資源。互斥在內核中實現,所以能夠進入內核模式。互斥不只能夠用於線程之間,也能夠用於進程之間。通常用於不一樣進程之間時,它都擁有一個唯一的名字,咱們稱這種互斥爲命名互斥。

  • Semaphore

爲了限制訪問共享資源的線程數量,咱們須要使用信號量。信號量是內核對象,它有一個計數變量用於表示當前正在使用共享資源的線程數。舉個例子,下面的代碼用MFCCSemaphore類建立了一個信號量,它能保證在設定的時間週期(這個值有第一個參數設定)內可以同時訪問共享資源的最大線程數量,而且初始化完成後的時刻沒有線程對資源擁有全部權(有第二個參數設定)。

HANDLE g_sem = CreateSemaphore(NULL,5,5,NULL);
  • 更多信息

若是想了解更多,請閱讀Arms S的這篇文章 here

4 鎖框架的目的

  在windows開發中,最多見的同步基元是Critical Section, Semaphore, Mutex. 對於初學者他們可能常用這些同步基元,卻很難去了解同步概念自己。所以創建一個簡單的鎖框架的出發點是使得不一樣基元擁有統一的接口和調用方式, 可是按照它們原始的機制去工做,這樣更容易理解。

5 BaseLock

  雖然不一樣的同步基元做用不同,可是仍然有一些基本的功能共同點。

  • Lock

  • TryLock

  • TryLockFor (試加鎖一段時間)

  • Unlock

  可能有人會想到更多的共同點,可是你能夠根據你的需求選擇構建你本身的鎖框架,這裏我只是用這4個函數來簡化咱們想要說明的問題。咱們的 BaseLock類是一個純虛類,以下所示:

// BaseLock.h
  
class BaseLock
{ 
public:
   BaseLock();
   virtual ~BaseLock();
   virtual bool Lock() =0;
   virtual long TryLock()=0;
   virtual long TryLockFor(const unsigned int dwMilliSecond)=0; 
   virtual void Unlock()=0; 
};

注意一點:對於互斥而言,Lock函數返回布爾型,對於其它的基元,這個函數老是返回真。

6 Critical Section

  由於CRITICAL_SECTION結構體已經存在了,因此這裏咱們將這個類命名爲CriticalSectionEx。這個類繼承自BaseLock類,也是CRITICAL_SECTION對象的一個接口類。與BaseLock類類似,咱們定義以下:

// CriticalSectionEx.h
#include "BaseLock.h"

class CriticalSectionEx :public BaseLock
{
public:
   CriticalSectionEx();
   CriticalSectionEx(const CriticalSectionEx& b);
   virtual ~CriticalSectionEx();
   CriticalSectionEx & operator=(const CriticalSectionEx&b)
   {
      return *this;
   }
   virtual bool Lock();
   virtual long TryLock();
   virtual long TryLockFor(const unsigned int dwMilliSecond);
   virtual void Unlock();
private:
   CRITICAL_SECTION m_criticalSection;
   int m_lockCounter;
}; 
  • 注意, 賦值操做符重載函數"operator="返回自身,是由於我不想讓成員對象CRITICAL_SECTION被改變,若是"operator="沒有被重載,它會自動調用拷貝函數,這樣CRITICAL_SECTION對象的值就會被修改替代。另外爲何沒有將賦值操做符函數設爲私有?緣由在於當一個類好比說A試圖從其它類拷貝時,訪問私有函數會產生編譯錯誤。

  • 另外,臨界區在重入時,若是進入和離開次數不對等將產生未定義行爲, 因此咱們加入了成員變量m_lockCounter,用來跟蹤locks/unlocks的數量。

下面的5個函數是臨界區使用時的經常使用函數:

  • InitializeCriticalSection

  • DeleteCriticalSection

  • EnterCriticalSection

  • LeaveCriticalSection

  • TryEnterCriticalSection

更多信息請參考MSDN。這5個函數將合併以下:

  Constructor/Copy Constructor

// CriticalSectionEx.cpp
CriticalSectionEx::CriticalSectionEx() :BaseLock()
{
   InitializeCriticalSection(&m_criticalSection);
   m_lockCounter=0;
}
CriticalSectionEx::CriticalSectionEx(const CriticalSectionEx& b):BaseLock()
{
   InitializeCriticalSection(&m_criticalSection);
   m_lockCounter=0; 
}

  Destructor

// CriticalSectionEx.cpp
CriticalSectionEx::~CriticalSectionEx()
{
   assert(m_lockCounter==0);
   DeleteCriticalSection(&m_criticalSection);
} 

  若是CRITICAL_SECTION對象被初始化後,它必需要釋放,因此將DeleteCriticalSection放在析構函數裏,CriticalSectionEx析構時CRITICAL_SECTION將被自動釋放。注意若是m_lockCounter不爲0,這意味着加鎖和解鎖次數不相同,這將致使未定義行爲。

  Lock/TryLock

// CriticalSectionEx.cpp
bool CriticalSectionEx::Lock()
{
   EnterCriticalSection(&m_criticalSection);
   m_lockCounter++
   return true;
}
long CriticalSectionEx::TryLock()
{
   long ret=TryEnterCriticalSection(&m_criticalSection);
   if(ret)
      m_lockCounter++;
   return ret;
}   

這是很是直觀的實現方式。不解釋了!

  TryLockFor

// CriticalSectionEx.cpp
long CriticalSectionEx::TryLockFor(const unsigned int dwMilliSecond)
{
   long ret=0;
   if(ret=TryEnterCriticalSection(&m_criticalSection))
   {
      m_lockCounter++;
      return ret;
   }
   else
   {
      unsigned int startTime,timeUsed;
      unsigned int waitTime=dwMilliSecond;
      startTime=GetTickCount();
      while(WaitForSingleObject(m_criticalSection.LockSemaphore,waitTime)==WAIT_OBJECT_0)
      {
         if(ret=TryEnterCriticalSection(&m_criticalSection))
         {
            m_lockCounter++;
            return ret;
         }
         timeUsed=GetTickCount()-startTime;
         waitTime=waitTime-timeUsed;
         startTime=GetTickCount();
      }
      return 0;
   }
}    

  注意,原始的TryEnterCriticalSection函數沒有時間參數(根據MSDN的說法是爲了防止死鎖)。所以我用一點技巧來模擬必定時間段內的TryLock機制,使用的時候你須要注意一點,由於微軟並無爲CRITICAL_SECTION對象實現這個接口。這裏並不保證進入臨界區的順序。

  Unlock

// CriticalSectionEx.cpp
void CriticalSectionEx::Unlock()
{
   assert(m_lockCounter>=0);
   LeaveCriticalSection(&m_criticalSection);
}   

  若是獲取到CRITICAL_SECTION對象,釋放時經過調用"LeaveCriticalSection"來解鎖表示離開臨界區。主要到m_lockCounter必須大於等於0,不然表示解鎖次數大於加鎖次數。

7 Semaphore Class

  咱們的信號量類也繼承自BaseLock類,是Semaphore對象的接口類 (其實是一個句柄)。以下:

// Semaphore.h
#include "BaseLock.h"
 
class Semaphore :public BaseLock
{
public:
   Semaphore(unsigned int count=1,const TCHAR *semName=_T(""), LPSECURITY_ATTRIBUTES lpsaAttributes = NULL);
   Semaphore(const Semaphore& b);
   virtual ~Semaphore();
   Semaphore & operator=(const Semaphore&b)
   {
      return *this;
   }
   virtual bool Lock();
   virtual long TryLock();
   virtual long TryLockFor(const unsigned int dwMilliSecond);
   virtual void Unlock();
   long Release(long count, long * retPreviousCount);
private:
   /// Actual Semaphore
   HANDLE m_sem;
   /// Creation Info
   LPSECURITY_ATTRIBUTES m_lpsaAttributes;
   /// Semaphore Flag
   unsigned int m_count;
};  

  注意, 信號量類的構造函數的參數有2個:semNamelpsaAttributes,而BaseLockCriticalSectionEx的構造函數沒有。另外它的構造函數調用時能夠不加參數,由於有默認參數,這幾個參數的意義以下:

    • count 信號量的加鎖次數

      • 缺省值是1,表明了二值信號量

    • semName 信號量名稱

      • 缺省值是NULL

      • 在不一樣進程間同步時須要設置這個參數 (參考MSDN.)

    • lpsaAttributes 新的信號量的安全屬性

      • 缺省值是NULL,表明了缺省的安全屬性

      • (想了解更多,參考MSDN.)

下面的函數是信號量的4個經常使用操做函數:

  • CreateSemaphore

  • CloseHandle

  • WaitForSingleObject

  • ReleaseSemaphore

更多信息可參考MSDN,這4個函數合併以下:

  Constructor/Copy Constructor

// Semaphore.cpp
 
Semaphore::Semaphore(unsigned int count,const TCHAR *semName, LPSECURITY_ATTRIBUTES lpsaAttributes) :BaseLock()
{ 
   m_lpsaAttributes=lpsaAttributes;
   m_count=count;
   m_sem=CreateSemaphore(lpsaAttributes,count,count,semName);
} 
Semaphore::Semaphore(const Semaphore& b) :BaseLock()
{ 
   m_lpsaAttributes=b.m_lpsaAttributes;
   m_count=b.m_count;
   m_sem=CreateSemaphore(m_lpsaAttributes,m_count,m_count,NULL);
}  

  這裏的拷貝構造函數,複製了另外一個類的安全屬性和計數值,而後建立一個新的信號量對象。

  Destructor

// Semaphore.cpp
 
Semaphore::~Semaphore()
{
   CloseHandle(m_sem);
} 

  Lock/TryLock/TryLockFor

// Semaphore.cpp
bool Semaphore::Lock()
{
   WaitForSingleObject(m_sem,INIFINITE);
   return true;
}
long Semaphore::TryLock()
{
   long ret=0;
   if(WaitForSingleObject(m_sem,0) == WAIT_OBJECT_0 )
      ret=1;
   return ret;
}
long Semaphore::TryLockFor(const unsigned int dwMilliSecond)
{
   long ret=0;
   if( WaitForSingleObject(m_sem,dwMilliSecond) == WAIT_OBJECT_0)
      ret=1;
   return ret;
}

  Unlock

// Semaphore.cpp
 
void Semaphore::Unlock()
{
   ReleaseSemaphore(m_sem,1,NULL);
} 

  Release

  如Ahmed Charfeddine建議的那樣,對於信號量的Unlock函數最好能夠接受一個表示釋放計數的參數並返回成功或失敗結果。這是信號量比較特別的地方,你能夠從新寫一個Release函數進行擴展。

// Semaphore.cpp
 
void Semaphore::Release(long count, long * retPreviousCount)
{
   return ReleaseSemaphore(m_sem,count,retPreviousCount); 
}
//用法
BaseLock *lock = new Semaphore(10);
...
BOOL ret = dynamic_cast<Semaphore*>(lock)->Release(5);
// OR
Semaphore lock(10);
...
BOOL ret = lock.Release(5);  



8 Mutex Class

  互斥類繼承自BaseLock,是互斥對象的一個接口類。

// Mutex.h
#include "BaseLock.h"



class Mutex :public BaseLock
{
public:
   Mutex(const TCHAR *mutexName=NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL);
   Mutex(const Mutex& b);
   virtual ~Mutex();
   Mutex & operator=(const Mutex&b)
   {
      return *this;
   }
   virtual bool Lock();
   virtual long TryLock();
   virtual long TryLockFor(const unsigned int dwMilliSecond);
   virtual void Unlock();
   bool IsMutexAbandoned()
   {
      return m_isMutexAbandoned;
   )
private:
   /// Mutex
   HANDLE m_mutex;
   /// Creation Security Info
   LPSECURITY_ATTRIBUTES m_lpsaAttributes;
   /// Flag for whether this mutex is abandoned or not.
   bool m_isMutexAbandoned;
}; 

  注意, 互斥量能夠被捨棄,可是咱們不想單獨爲了互斥量改變統一的接口,因此我增長了一個函數IsMutexAbandoned用來檢查此互斥量是否在加鎖失敗時被捨棄了。

互斥量的經常使用函數以下:

  • CreateMutex

  • CloseHandle

  • WaitForSingleObject

  • ReleaseMutex

更多信息請參考MSDN.

  Constructor/Copy Constructor

// Mutex.cpp
Mutex::Mutex(const TCHAR *mutexName, LPSECURITY_ATTRIBUTES lpsaAttributes) :BaseLock()
{
   m_isMutexAbandoned=false;
   m_lpsaAttributes=lpsaAttributes;
   m_mutex=CreateMutex(lpsaAttributes,FALSE,mutexName);
}
Mutex::Mutex(const Mutex& b)
{
   m_isMutexAbandoned=false; 
   m_lpsaAttributes=b.m_lpsaAttributes;
   m_mutex=CreateMutex(m_lpsaAttributes,FALSE,NULL);
}  

  Destructor

// Mutex.cpp
 
Mutex::~Mutex()
{
   CloseHandle(m_mutex);
} 

  Lock/TryLock/TryLockFor

// Mutex.cpp
void Mutex::Lock()
{
   bool returnVal = true;
   unsigned long res=WaitForSingleObject(m_mutex,INIFINITE);
   if(res=WAIT_ABANDONED)
   {
      m_isMutexAbandoned=true;
      returnVal=false; 
   }
   return returnVal;
}
long Mutex::TryLock()
{
   long ret=0;
   unsigned long mutexStatus= WaitForSingleObject(m_mutex,0);
   if(mutexStatus== WAIT_OBJECT_0)
      ret=1;
   else if(mutexStatus==WAIT_ABANDONED)
      m_isMutexAbandoned=true;
   return ret;
}
long Mutex::TryLockFor(const unsigned int dwMilliSecond)
{
   long ret=0; 
   unsigned long mutexStatus= WaitForSingleObject(m_mutex,dwMilliSecond);
   if(mutexStatus==WAIT_OBJECT_0)
      ret=1;
   else if(mutexStatus==WAIT_ABANDONED)
      m_isMutexAbandoned=true;
   return ret;
}   

  調用Lock函數時,無限等待直到獲取互斥對象,當WaitForSingleObject函數返回時便自動獲取了互斥對象。調用TryLock時,不等待直接試圖獲取互斥對象,而TryLockFor會在給定之間內試着獲取互斥對象,更多參考信息請看 MSDN。注意:對於全部的加鎖操做,會判斷互斥量是否被捨棄,並重置m_isMutexAbandoned狀態。所以加鎖失敗時,你能夠調用IsMutexAbandoned查看是不是由於互斥量被捨棄引發的。

  Unlock

// Mutex.cpp
 
void Mutex::Unlock()
{
   ReleaseMutex(m_mutex);
}

9 NoLock Class

  對於鎖框架而言,NoLock類在單線程環境下,只是一個佔位符(個人註釋:佔位符的意思就是不包含具體的操做函數,只是爲了保持與其它環境下好比多線程,多進程的使用一致性,可參見後面的示例代碼)。咱們的Nolock類繼承於 BaseLock類,以下:

// NoLock.h
#include "BaseLock.h"
 
class NoLock :public BaseLock
{
public:
   NoLock();
   NoLock(const NoLock& b);
   virtual ~NoLock();
   NoLock & operator=(const NoLock&b)
   {
      return *this;
   }
   virtual bool Lock();
   virtual long TryLock();
   virtual long TryLockFor(const unsigned int dwMilliSecond);
   virtual void Unlock();
private:
};   

  正如上面所說,NoLock類只是一個佔位符,因此沒有任何成員變量,也沒有鎖機制函數。

  Implementation

// NoLock.cpp
NoLock::NoLock() :BaseLock()
{
}
NoLock::NoLock(const NoLock& b):BaseLock()
{
}
NoLock::~NoLock()
{
}
bool NoLock::Lock()
{
   return true;
}
long NoLock::TryLock()
{
   return 1;
}
long NoLock::TryLockFor(const unsigned int dwMilliSecond)
{
   return 1;
}
void NoLock::Unlock()
{
}  

NoLock沒有作任何事情,只是有返回值時返回真。

10 Auto-release Lock

  當進行同步操做時,匹配加鎖和解鎖是一件很是惱火的事情,因此建立一個擁有自動釋放機制的結構體是很是有必要的。咱們能夠擴展BaseLock類並按照以下的代碼去實現:

// BaseLock.h
 
Class BaseLock
{
public:
...
   class BaseLockObj
   {
   public:
      BaseLockObj(BaseLock *lock);
      virtual ~BaseLockObj();
      BaseLockObj &operator=(const BaseLockObj & b)
      {
         return *this;
      }
   private:
      BaseLockObj();
      BaseLockObj(const BaseLockObj & b){assert(0);m_lock=NULL;}
      /// The pointer to the lock used.
      BaseLock *m_lock;
   }; 
   ... 
};  
/// type definition  for lock object 
typedef BaseLock::BaseLockObj LockObj; 

  重載拷貝操做函數,並設爲私有,是爲了防止從其它對象進行拷貝。缺省的構造函數被設爲私有,因此必須經過傳遞指向BaseLock對象的指針來調用構造函數。

全局聲明 BaseLock::BaseLockObjLockObj,便於訪問。


  Implementation

// BaseLock.cpp 
 
BaseLock::BaseLockObj::BaseLockObj(BaseLock *lock)
{
   assert(lock); 
   m_lock=lock;
   if(m_lock) 
      m_lock->Lock();
} 
 
BaseLock::BaseLockObj::~BaseLockObj()
{
   if(m_lock)
   {
      m_lock->Unlock();
   }
} 
 
BaseLock::BaseLockObj::BaseLockObj()
{ 
   m_lock=NULL;
} 

  如代碼所示,當BaseLockObj 被建立時,調用構造函數並執行Lock,自動加鎖。

同理,析構時,解鎖被調用執行。

這些實現對於全部的同步基元都適用,包括 NoLock,所以它們擁有了統一的接口,而且都繼承自BaseLock類。

示例:

...
BaseLock *someLock = new CriticalSectionEx(); // declared in somewhere accessible
...
void SomeThreadFunc()
{ 
   ...
   if (test==0)
   {
      LockObj lock(someLock);
      // Lock is obtained automatically
      ... 
   } /// When leaving the if statement lock object is destroyed, so the Lock is automatically released
   ...    
} 
... 

 

11 Use-case Examples

  • 聲明同步對象
 ... 
   // MUTEX 
   BaseLock * pLock = new Mutex (); 
   // OR 
   Mutex cLock; 
   ...
   // SEMAPHORE
   BaseLock * pLock = new Semaphore();
   // OR 
   Semaphore cLock;
   ...
   // CRITICAL SECTION
   BaseLock * pLock = new CriticalSectionEx();
   // OR
   CriticalSectionEx cLock;
   ...
   // NOLOCK
   BaseLock *pLock = new NoLock();
   // OR
   NoLock cLock;  
   ... 

  同一應用程序,對於不一樣的環境好比單線程,多線程,多進程均可以這樣聲明處理過程:

...

BaseLock *pLock=NULL; 
#if SINGLE_THREADED
pLock = new NoLock();
#elif MULTI_THREADED
pLock = new CriticalSectionEx();
#else // MULTI_PROCESS
pLock = new Mutex();
#endif
...   
  • 聲明信號量對象,附帶初始鎖計數

...
BaseLock * pLock = new Semaphore(2);
// OR
Semaphore cLock(2);
...   
  • 聲明用於跨進程的互斥量/信號量

... 
BaseLock * pLock = new Mutex (_T("MutexName")); 
//OR  
Mutex cLock(_T("MutexName"));
...
 
// For semaphore, lock count must be input, in order to give semaphore name 
BaseLock * pLock = new Semaphore(1, _T("SemaphoreName")); 
//OR 
Semaphore cLock(1, _T("SemaphoreName"));  
... 

對於信號量,此時應該給定初始計數值,而後設定名稱。

 

12 示例1 (手動加鎖)

void SomeFunc(SomeClass *sClass)
{ 
   ... 
   pLock->Lock();   //OR cLock.Lock();
   ... 
   pLock->Unlock(); //OR cLock.Unlock();
} 

 

13 示例2 (自動加鎖)

void SomeFunc()
{ 
   LockObj lock(pLock); //OR LockObj lock(&cLock);
   ...
} // Lock is auto-released when exiting the function.

或者這樣:

void SomeFunc(bool doSomething)
{ 
   ... 
   if(doSomething)
   { 
      LockObj lock(pLock); //OR LockObj lock(&cLock);
      ...
   }// Lock is auto-released when exiting the if-statement.
   ...
}  

 

14 示例3(TryLock/TryLockFor)

... 
if(pLock->TryLock()) //OR cLock.TryLock()
{
   // Lock obtained
   ...
   pLock->Unlock(); //OR cLock.Unlock();
}  
... 
或者:
...
if(pLock->TryLockFor(100)) //OR cLock.TryLockFor(100) // TryLock for 100 millisecond
{
   // Lock obtained
   ...
   pLock->Unlock(); //OR cLock.Unlock();
}  

 

15 更多示例

  更多的例子能夠查看 EpServerEngine 的源代碼。更多的細節能夠看這篇文章:

("EpServerEngine - A lightweight Template Server-Client Framework using C++ and Windows Winsock" )  


16 Reference

 

License

This article, along with any associated source code and files, is licensed under The MIT License

********************************************************************************

原文:http://www.codeproject.com/Articles/421976/How-to-create-a-Simple-Lock-Framework-for-Cplusplu

 源代碼:http://files.cnblogs.com/wb-DarkHorse/SimpleLockFramework.zip

相關文章
相關標籤/搜索