數據保護
同許多線程API一樣,C++0x用互斥來保護共享數據。有四種互斥類型:
Non-recursive (std::mutex)
Recursive (std::recursive_mutex)
允許鎖超時的non-recursive (std::timed_mutex)
允許鎖超時的recursive (std::recursive_timed_mutex)
如果你試圖在一個線程上鎖(lock)一個non-recursive mutex兩次而當中沒有unlock它的話,會產生未知結果。遞歸recur6sive mutex只是增加鎖的計數,因此必須確保你unlock和lock的次數相同,其他線程才可能鎖這個mutex。
通常我們用模板類std::unique_lock<>和std::lock_guard<>來lock和unlock一個mutex。這些類在構造函數中lock一個mutex,在析構函數中unlock它。因此,如果你用的是局部變量,你的mutex會在退出作用域時自動被unlock。
std::mutex m;
my_class data;
void foo()
{
std::lock_guard<std::mutex> lk(m);
process(data);
} // mutex unlocked here
std::lock_guard只能像上面這樣使用。而std::unique_lock允許延遲lock、設置超時,以及在對象銷毀之前unlock。如果你選擇std::timed_mutex來設置鎖超時的話,那需要使用std::unique_lock:
std::timed_mutex m;
my_class data;
void foo()
{
std::unique_lock<std::timed_mutex>
lk(m,std::chrono::milliseconds(3)); // wait up to 3ms
if(lk) // if we got the lock, access the data
process(data);
} // mutex unlocked here
由於這些lock類是模板,因此他們可以用於所有標准的mutex類型,以及提供了lock()和unlock()函數的擴展類型。
避免死鎖
有時候,我們需要鎖多個mutex。如果控制不力,可能導致死鎖(deadlock):兩個線程都試圖鎖相同的mutex,每個線程都鎖住一個mutex,而等待另外一個線程釋放其他的mutex。C++0x考慮到了這個問題,你可以使用std::lock函數來一次鎖住多個mutex,而不必冒著死鎖的危險來一個一個地鎖:
struct X
{
std::mutex m;
int a;
std::string b;
};
void foo(X& a,X& b)
{
std::unique_lock<std::mutex> lock_a(a.m,std::defer_lock);
std::unique_lock<std::mutex> lock_b(b.m,std::defer_lock);
std::lock(lock_a,lock_b);
// do something with the internals of a and b
}
在上面的例子中,如果你不使用std::lock的話,將很可能導致死鎖(如一個線程執行foo(x,y), 另一個執行foo(y,x))。加上std::lock後,則是安全的。