一般來說,std::map從不同執行緒訪問同一個實體不是執行緒安全的。
但是在這種情況下它是否可以是執行緒安全的:
std::map初始化后,不會再將元素添加到\從實體中洗掉- 的值的型別
std::map是std::atomic<T>
這是演示代碼:
#include<atomic>
#include<thread>
#include<map>
#include<vector>
#include<iostream>
class Demo{
public:
Demo()
{
mp_.insert(std::make_pair(1, true));
mp_.insert(std::make_pair(2, true));
mp_.insert(std::make_pair(3, true));
}
int Get(const int& integer, bool& flag)
{
const auto itr = mp_.find(integer);
if( itr == mp_.end())
{
return -1;
}
else
{
flag = itr->second;
return 0;
}
}
int Set(const int& integer, const bool& flag)
{
const auto itr = mp_.find(integer);
if( itr == mp_.end())
{
return -1;
}
else
{
itr->second = flag;
return 0;
}
}
private:
std::map<int, std::atomic<bool>> mp_;
};
int main()
{
Demo demo;
std::vector<std::thread> vec;
vec.push_back(std::thread([&demo](){
while(true)
{
for(int i=0; i<9; i )
{
bool cur_flag = false;
if(demo.Get(i, cur_flag) == 0)
{
demo.Set(i, !cur_flag);
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
}));
vec.push_back(std::thread([&demo](){
while(true)
{
for(int i=0; i<9; i )
{
bool cur_flag = false;
if(demo.Get(i, cur_flag)==0)
{
std::cout << "(" << i << "," << cur_flag <<")" << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
})
);
for(auto& thread:vec)
{
thread.join();
}
}
更重要的是,編譯器不會抱怨任何-fsanitize=thread選項。
uj5u.com熱心網友回復:
是的,這是安全的。
最好將資料競爭視為不同步的沖突訪問(潛在的并發讀取和寫入)。
std::thread構造強加了一個順序:代碼中前面的動作保證在執行緒開始之前出現。因此,在并發訪問之前,地圖已完全填充。
該庫表示標準型別只能訪問型別本身、函式引數和任何容器元素的必需屬性。std::map::find是非常量,但標準要求出于資料競爭的目的,將其視為 const。迭代器上的操作最多需要訪問(但不修改)容器。所以并發訪問std::map都是非修改的。
這使得加載和存盤std::atomic<bool>也沒有競爭。
uj5u.com熱心網友回復:
這應該避免資料爭用 UB,因為在啟動任一執行緒后根本不會改變std::map資料結構。鑒于您修改地圖的原子值的方式有限,這也是安全的。
您沒有提供允許原子 RMW 的訪問器函式,因此您唯一能做的就是.store(flag)使用.load(flag)默認的memory_order_seq_cst. 不是.exchangeor .compare_exchange_weak, or^= 1翻轉。
您if(Get) Set不等同于flag ^= 1;該標志的原子翻轉(盡管它的編譯效率不如人們所希望的:切換 atomic_bool 的有效方法)。
如果另一個執行緒也翻轉相同的標志,他們可以互相踩踏。if(Get) Set例如,總共 10 次翻轉應該將其恢復為原始值,但是通過單獨的原子加載和存盤,兩個執行緒可以加載相同的值然后存盤相同的值,導致跨多個操作的 2 次(或更多)操作只有一次翻轉執行緒。
當然,如果您沒有多個執行緒撰寫相同的標志,那么像您正在做的那樣單獨加載和存盤會更有效。
特別是如果您避免使用 default memory_order_seq_cst,例如為std::memory_order ord = std::memory_order_seq_cst您的訪問器函式提供一個可選引數,就像 std::atomic 函式一樣。大多數 ISA 上的 SC 商店的成本更高。(特別是在 x86 上,甚至mo_release在 asm 中是免費的,但mo_seq_cst需要一個完整的屏障。)
(但正如在切換 atomic_bool 的有效方式中 std::atomic<bool>所討論的那樣,除了 之外,沒有可移植的方式來原子地翻轉它^= 1,沒有可以采用 memory_order 引數的成員函式。 atomic<uint8_t>可能是更好的選擇,將低位作為實際布林值價值。)
由于您需要能夠回傳失敗,因此可能回傳指向物件的指標,std::atomic<bool>NULL 表示未找到。這將讓呼叫者使用任何 std::atomic 成員函式。但它確實使濫用成為可能,將參考保留太久。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/495870.html
