# Windows Critical Section API
### Mutex鎖的概念
假設有以下運算要進行:
0 + 3 + 5 - 2 + 7
val = 0;
如果是Single Thread下,可以照順序寫下所有運算
val = val + 3;
val = val + 5;
val = val - 2;
val = val + 7;
如果要讓多個thread各自去執行一部分運算,在沒有考慮thread-safe的情形下,有可能發生以下錯誤:
** Thread的執行順序是不確定的
// Thread A負責+3
// 假設取到的val是0
val = val + 3;
result: 3
// Thread B負責+5
// 假設這時thread A還未將結果存到val,因此取到的值還是0
val = 0 + 5;
result: 5
// Thread C負責-2
val = val - 2; // 假設取到的val是Thread A運算完的3
result: 1
// Thread D負責+7 // 假設取到的val是thread C運算完的1
val = val + 7;
result: 8
可以看到結果完全不正確;要避免這個結果,只要確保每一道運算在同一時間裡不同時進行即可,因此我們會寫一個簡單function來處理單一運算,並且將這個function用critical section保護起來
(這裡有一個限制是threads必須拿global變數val做運算,而不是用threads自己持有的local變數,最後再由主程式去統整結果。雖然後者就不會有thread-safe的問題,但這樣就失去multi-thread的意義)
### Windows Critical Section API
每個支援multi-thread的作業系統應該提供相應的資料保護機制,以避免資源競爭的問題,Windows則是提供一組Critical Section API:
InitializeCriticalSection
EnterCriticalSection
LeaveCriticalSection
DeleteCriticalSection
實際使用方式如下:
```cpp
#include <windows.h>
CRITICAL_SECTION g_mtx;
int g_val = 0;
int g_operand[] = {3, 5, -2, 7};
int main()
{
InitializeCriticalSection(g_mtx);
// start threads
DWORD threadID[4];
HANDLE threadHandle[4];
for (int i = 0; i < 4; ++i) {
threadHandle[i] = CreateThread(0, 0, thread_proc, i, 0, &threadID[i]);
}
DeleteCriticalSection(g_mtx);
return 0;
}
DWORD WINAPI thread_proc(LPVOID lpParameter)
{
int index = (int)lpParameter;
add(g_operand[index]);
}
void add(int Operant)
{
EnterCriticalSection(g_mtx);
g_val = g_val + Operant;
LeaveCriticalSection(g_mtx);
}
```
因為單純的加減法運算符合結合律交換律,無論四個thread順序如何排列最終結果都會正確
這邊CRITICAL_SECTION是一個鎖的概念,如果一個function會被多個thread執行,每個thread
在進入critical section時必須傳遞同一個鎖,如果傳遞不同一個鎖,則無法達到保護目的
### 設計模式 (Design Pattern)
設計模式是將一系列行為架構用物件導向的方式封裝起來,使得程式在重複該行為時可以做得更簡潔,以下是兩種Critical Section的設計模式 (ObjectLevelLockable和ClassLevelLockable):
```cpp
#ifndef ThreadsH
#define ThreadsH
//-----------------------------------------------------------------------------
#include "Pch.h"
//-----------------------------------------------------------------------------
// 這個class只是簡單地將Critical Section API包裝成一個class,沒什麼特別
class Mutex
{
public:
Mutex()
{
::InitializeCriticalSection(&_mtx);
}
~Mutex()
{
::DeleteCriticalSection(&_mtx);
}
void Lock()
{
::EnterCriticalSection(&_mtx);
}
void Unlock()
{
::LeaveCriticalSection(&_mtx);
}
private:
mutable CRITICAL_SECTION _mtx;
};
// 這個class利用了destructor,使得LeaveCriticalSection在其使用函式結束時會被自動呼叫
template < class Host, class MutexPolicy = Mutex >
class ObjectLevelLockable
{
private :
mutable MutexPolicy _mtx;
public:
ObjectLevelLockable() : _mtx() {}
ObjectLevelLockable(const ObjectLevelLockable&) : _mtx() {}
~ObjectLevelLockable() {}
class Lock;
friend class Lock;
class Lock
{
public:
explicit Lock(const ObjectLevelLockable& host) : _host(host)
{
_host._mtx.Lock();
}
explicit Lock(const ObjectLevelLockable* host) : _host(*host)
{
_host._mtx.Lock();
}
~Lock()
{
_host._mtx.Unlock();
}
private:
Lock();
Lock(const Lock&);
Lock& operator=(const Lock&);
const ObjectLevelLockable& _host;
};
};
//-----------------------------------------------------------------------------
// 這個class將整個Mutex鎖變成全域的,只要是繼承它的子class
// 無論該class產生了幾個object實例,在呼叫function時都會參考到同一個鎖
template <class Host, class MutexPolicy = Mutex >
class ClassLevelLockable
{
private :
class Initializer
{
public :
Initializer() : _init(true), _mtx()
{
}
~Initializer()
{
}
public :
bool _init;
MutexPolicy _mtx;
};
static Initializer _initializer;
public:
class Lock;
friend class Lock;
class Lock
{
public:
Lock()
{
_initializer._mtx.Lock();
}
explicit Lock(const ClassLevelLockable&)
{
_initializer._mtx.Lock();
}
explicit Lock(const ClassLevelLockable*)
{
_initializer._mtx.Lock();
}
~Lock()
{
_initializer._mtx.Unlock();
}
private:
Lock(const Lock&);
Lock& operator=(const Lock&);
};
};
template < class Host, class MutexPolicy >
typename ClassLevelLockable< Host, MutexPolicy >::Initializer
ClassLevelLockable< Host, MutexPolicy >::_initializer;
//-----------------------------------------------------------------------------
#endif
```
### 說明
ObjectLevelLockable和ClassLevelLockable目標是打造為繼承的class提供multu-thread保護
假設以下場景,有一個ClassA全域變數,裡面儲存了一項數值,並且有用來加減該數值的function,該數值需要被保護以確保不會被同時存取
```cpp
static ClassA g_ca;
// Thread裡面會呼叫到存取單一資源的function,因此該function在multi-thread裡不能被同時呼叫
DWORD WINAPI thread_proc(LPVOID lpParameter)
{
// 隨機產生加減值
int value = rand();
bool sign = ((rand() % 100 + 1) - (rand() % 100 + 1)) > 50 ? true : false;
if (sign)
value = -value;
g_ca.add(value);
}
```
如果單純只使用Critical Section API,ClassA設計如下:
```cpp
class ClassA
{
public:
// Constructor
ClassA()
{
InitializeCriticalSection(_mtx);
}
~ClassA()
{
DeleteCriticalSection(_mtx);
}
void add(int value)
{
EnterCriticalSection(_mtx);
// The code to protect
resource += value
LeaveCriticalSection(_mtx);
}
// 讀取最新的resource值
int get_resource_val() const
{
return _resource;
}
private:
mutable CRITICAL_SECTION _mtx;
int _resource;
};
```
如果使ClassA繼承ObjectLevelLockable,程式會比較簡潔
```cpp
class ClassA : public ObjectLevelLockable<ClassA>
{
public:
// Constructor
ClassA()
{
}
~ClassA()
{
}
void add(int value)
{
Lock lock(*this);
// The code to protect
_resource += value
}
// 讀取最新的resource值
int get_resource_val() const
{
return _resource;
}
private:
int _resource;
};
```
如果是一個class同時可能有多個object,這些object都可能存取到同一項資源,則需要用ClassLevelLockable
須注意這裡的_resource宣告使用了static,也就是它是一個全域變數,它的生命週期是整個程式開始時就存在,而不是隨著ClassA object的產生才存在
(上一個範例中,只有一個全域的ClassA object,因此_resource不需要宣告為static;下個範例中ClassA可能有很多個,用來操作同一個變數_resource,因此_resource必須是全域)
```cpp
class ClassA : public ClassLevelLockable<ClassA>
{
public:
// Constructor
ClassA()
{
}
~ClassA()
{
}
void add(int value)
{
Lock lock(*this);
// The code to protect
_resource += value
}
// 讀取最新的resource值
int get_resource_val() const
{
return _resource;
}
private:
static int _resource;
};
```
```cpp
DWORD WINAPI thread_proc(LPVOID lpParameter)
{
// ClassA不再是全域變數,而是隨著thread產生的區域變數,因此可能同時有很多個
ClassA ca;
int value = rand();
bool sign = ((rand() % 100 + 1) - (rand() % 100 + 1)) > 50 ? true : false;
if (sign)
value = -value;
ca.add(value);
}
```
在使用ClassLevelLockable之後,只要宣告了ClassLevelLockable<ClassA>,那麼整個程式的生命週期中,就存在一個用來保護ClassA中需要保護的function的鎖,無論有幾個ClassA object實例,都會使用同一個鎖來保護資料