書中提到假設有下面代碼:
MutexLock mutex;
std::vector<Foo> foos;
void post(const Foo &f) {
MutexLockGuard lock(mutex);
foos.push_back(f);
}
void traverse() {
MutexLockGuard lock(mutex);
for(std::vector<Foo>::const_iterator it = foos.begin(); it != foos.end(); ++ it) {
it -> doit();
}
}
如果 doit 中調用了 post 函數。
- 如果鎖是非遞歸的,那麼將會發生死鎖。
- 如果鎖是遞歸的,那麼 push_back 可能導致 vector 擴容,而造成迭代器失效
一種解決方式是,copy-on-write
typedef std::vector<Foo> FooList;
typedef boost::shared_ptr<FooList> FooListPtr;
MutexLock mutex;
FooListPtr g_foos;
// 對於 g_foos 來說,此函數是讀端
void traverse() {
FooListPtr foos;
{
MutexLockGuard lock(mutex); // 保護 shared_ptr
foos = g_foos;
}
for(std::vector<Foo>::const_iterator it = foos -> begin(); it != foos -> end(); ++ it) {
it -> doit();
}
}
// 對於 g_foos,此函數是寫端
void post(const Foo &f) {
printf("post\n");
MutexLockGuard lock(mutex);
if(!g_foos.unique()) {
g_foos.reset(new FooList(*g_foos));
printf("copy the whole list\n"); // 練習:將這句話移除臨界區
}
assert(g_foos.unique());
g_foos -> push_back(f);
}
即使 doit() 中調用了 post,仍不會發送死鎖,這是很顯然的,在 traverse 臨界區之後,mutex 就已經是未加鎖狀態。同時,也不會出現數據錯誤,因爲採用了 COW。
錯誤1:直接修改 g_foos 所指的 FooList
void post(const Foo& f) {
MutexLockGuard lock(mutex);
g_foos -> push_back(f);
}
這個配合着 shared_ptr 的 traverse(),顯然是錯誤的。
錯誤2:試圖縮小臨界區,把 copying 移除臨界區
void post(const Foo &f) {
FooListPtr newFoos(new FooList(*g_foos));
newFoos -> push_back(f);
MutexLockGuard lock(mutex);
g_foos = newFoos;
}
將 copying 放在臨界區外,沒有保證數據的一致性
錯誤3:把臨界區拆成兩個小的,把 copying 放到臨界區外
void post(const Foo &f) {
FooListPtr oldFoos;
{
MutexLockGuard lock(mutex);
oldFoos = g_foos;
}
FooListPtr newFoos(new FooList(*oldFoos));
newFoos -> push_back(f);
MutexLockGuard lock(mutex);
g_foos = newFoos;
}
這裏的問題其實和錯誤2問題一樣,只是看起來先複製了以下 g_foos,但是其對象的數據一致性仍沒有保證