借 shared_ptr 實現 copy-on-write,將代碼移除臨界區,避免死鎖

書中提到假設有下面代碼:

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,但是其對象的數據一致性仍沒有保證

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章