[英]avoiding multiple mutex locks for protecting similar race conditions
假設我有一些代碼使我處於競爭狀態,例如
class foo
{
some_data data;
public:
void bar(some_type arg)
{
// may change data
}
// ...
};
在沒有保護的情況下使用foo::bar()
不會是線程安全的,因為另一個線程可能會同時調用bar()
。 因此,可以說,更好的選擇是
class foo
{
some_data data;
std::mutex my_mutex;
void unsafe_bar(some_type arg);
public
void bar(some_type arg)
{
std::lock_guard<std::mutex> lock(my_mutex);
unsafe_bar(arg);
}
};
但是,假設有許多相似的類,其成員函數也存在同樣的問題
class foo1 { public: void bar(some_type arg); /*...*/ };
class foo2 { public: void bar(some_type arg); /*...*/ };
class foo3 { public: void bar(some_type arg); /*...*/ };
class foo4 { public: void bar(some_type arg); /*...*/ };
class foo5 { public: void bar(some_type arg); /*...*/ };
每個都在一個代碼塊中調用:
void work(some_type arg, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)
{
f1.bar(arg);
f2.bar(arg);
f3.bar(arg);
f4.bar(arg);
f5.bar(arg);
}
現在,每個調用都會鎖定和解鎖另一個互斥鎖,這似乎效率很低。 最好只使用一個互斥鎖。
問:是否有推薦/最佳方法?
我在考慮以下設計:
class foo
{
std::mutex my_mutex;
void unsafe_bar(some_type arg);
public:
template<typename Mutex>
void bar(some_type arg, Mutex&m)
{
std::lock_guard<Mutex> lock(m);
unsafe_bar(arg);
}
void bar(some_type arg)
{
bar(arg,my_mutex);
}
};
現在,用戶可以依靠foo::my_mutex
(第二個版本的foo::bar
),或提供互斥鎖,可以是std::recursive_mutex
或null_mutex
(滿足互斥鎖概念,但不執行任何操作)。 問 :這是一個明智/有用的想法嗎?
編輯我注意到我的比賽實際上並不依賴於任何特定的迭代器(及其可能的無效性等)。 我刪除了對迭代器的任何引用。
tl; dr最終,如何在對象上設置線程同步取決於您自己,並且在從單獨的對象調用互斥lock
的鎖時似乎效率不高,請在總體設計中考慮到這一點並弄清楚所需的“位置”您的互斥量/信號量和其他同步對象可以最好地處理與使代碼“線程安全”相關的開銷。 而且,如果您正在編寫將在外部使用的代碼(例如庫代碼),則需要確保您記錄特定功能實際上是“線程安全的”,否則我(作為用戶)將自己承擔該功能保護我自己的代碼。
void work(some_iterator const&i, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)
{
f1.bar(i);
f2.bar(i);
f3.bar(i);
f4.bar(i);
f5.bar(i);
}
問:是否有推薦/最佳方法?
是的,將互斥鎖從foo
對象中取出並放在其他位置;
// defined somewhere
std::mutex _mtx;
void work(some_iterator const&i, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)
{
_mtx.lock();
f1.bar(i);
f2.bar(i);
f3.bar(i);
f4.bar(i);
f5.bar(i);
_mtx.unlock();
}
問template<typename Mutex> void bar(some_iterator const&i, Mutex&m)
是明智/有用的主意?
不,如上面的示例所示,調用“全局”互斥/ semphore對象要容易得多(也更簡潔); 如果我想使用您的模板化函數執行相同的示例,則將需要進行額外的鍵入,進行額外的調用,並且可能不會達到我想要的效果,例如:
// defined somewhere
std::mutex _mtx;
void work(some_iterator const&i, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)
{
f1<std::mutex>.bar(i, _mtx);
f2<std::mutex>.bar(i, _mtx);
f3<std::mutex>.bar(i, _mtx);
f4<std::mutex>.bar(i, _mtx);
f5<std::mutex>.bar(i, _mtx);
}
我必須專門了解work
函數中的互斥鎖,因為我的some_iterator
可能會在工作函數之外進行修改(即使每個foo
對象都有一個互斥鎖),例如:
void work(some_iterator const&i, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)
{
// i could be modified here
f1.bar(i); // here if mutex handle != others
// here
f2.bar(i); // here if mutex handle != others
// here
f3.bar(i); // here if mutex handle != others
// here
f4.bar(i); // here if mutex handle != others
// here
f5.bar(i); // here if mutex handle != others
// here
}
? 當然,這可能就是您 ? 如果work
函數本質上不是設計成原子的,那么我(作為函數的用戶)可能在work
函數周圍放置了互斥鎖,例如(假設上面的代碼):
std::mutex _mtx2;
void some_thread_fn1()
{
_mtx2.lock();
// some other code
work(i, f1, f2, f3, f4, f5);
_mtx2.unlock();
}
void some_thread_fn2()
{
_mtx2.lock();
// some other code
work(i, f1, f2, f3, f4, f5);
_mtx2.unlock();
}
曾經使用過work
功能的人員將需要(某種程度上)知道其中調用了互斥鎖,否則他們可能會雙重保護(違背了您的初衷)。
還要注意,您的代碼實際上都不會鎖定相同的互斥鎖(假設foo
類中也包含std::mutex
),例如:
class foo1
{
some_data data;
std::mutex my_mutex;
void unsafe_bar(some_iterator const&i);
public:
void bar(some_iterator const&i)
{
std::lock_guard<std::mutex> lock(my_mutex);
unsafe_bar(i);
}
};
class foo2
{
some_data data;
std::mutex my_mutex;
void unsafe_bar(some_iterator const&i);
public:
void bar(some_iterator const&i)
{
std::lock_guard<std::mutex> lock(my_mutex);
unsafe_bar(i);
}
};
foo1 f1;
foo2 f2;
some_iterator x;
void thread1()
{
f1.bar(x);
}
void thread2()
{
f2.bar(x);
}
上面的代碼會導致爭用情況,因為您要鎖定2個單獨的互斥鎖(邏輯錯誤,因為您想鎖定同一互斥鎖)。
正如另一個答案指出的那樣,這個問題似乎更多地是關於多線程設計及其最佳實踐的。
希望能對您有所幫助。
有推薦/最佳的方法嗎?
我認為您的做法很普遍,也是避免比賽狀況的好方法。
這是一個明智/有用的想法嗎?
我認為第二種方法值得懷疑。 您將把同步的負擔放在班級的客戶/用戶身上。 至少在使用模板化函數時。 但是,如果執行此操作,則可以只聲明類不是線程安全的,並且調用者必須在所有情況下都將其同步。 至少是一致的。
通常,您應該在數據上並發訪問的數據成員上進行同步。 互斥鎖的數量更多地與粒度有關。 例如,始終鎖定一個完整的塊或僅鎖定讀/寫操作。 但這取決於用例。
編輯 :由於您現在發布了一些代碼。
它仍然取決於您要如何使用您的類。
f1到f5僅在
void work(some_iterator const&i, foo1&f1, foo2&f2, foo3&f3, foo4&f4, foo5&f5)
方法。 然后,根本不要在它們中使用互斥體。 類的用戶很容易進行同步。 work
的互斥。
這些類是否與工作方法分開使用?
同樣,答案將是保護與其自己的互斥鎖一起使用的每個類成員,因為對於客戶端而言,這將更加困難。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.