Когда Я пытаюсь получить доступ к myMap, использую myMutex, как показано ниже:
void insert(const std::string& Key)
{
std::lock_guard lock(myMutex);
if(!myMap.contains(Key))
{
auto& v = myMap[Key];
// do something to v to make it not empty
}
}
std::vector& get(const std::string& Key)
{
std::lock_guard lock(myMutex);
static std::vector empty; // this container will be read only
if(myMap.contains(Key))
{
auto& v = myMap[Key];
return v;
}
return empty;
}
Будет ли потокобезопасен, если я попытаюсь изменить контейнер?
std::vector& apples = get("Apple");
if(!apples.empty()) // check for existance
{
//do something to apples
}
из одного потока (я не использую myMutex, когда пытаюсь изменить яблоки) и вызываю функцию
из одного потока (я не использую myMutex, когда пытаюсь изменить яблоки) и вызываю функцию
р>
insert("AnotherApple");
из другого потока?
В моем случае я синхронизирую с помощью вставки и получаю, но я этого не делаю синхронизируйтесь с ними (особенно вставьте, которая изменит unordered_map), когда я вношу некоторые изменения в ссылочное значение (здесь — яблоки-контейнеры) указанного ключа, полученного из get. Но я буду использовать другой мьютекс (кроме myMutex) для синхронизации с этими изменениями, чтобы предотвратить запись содержимого одного и того же ключа из разных потоков.
Похоже, что в https ://stackoverflow.com/questions/25994312/can-i-access-a-c11-stdmap-entry- while-inserting-erasing-from-another-thread, первый ответ Якка - Адама Невраумон говорит:
Вы также можете изменить неключевой компонент элемента карты или набора, в то время как другие операции, которые это делают, не читать/записывать/уничтожать запуск неключевого компонента указанного элемента (а это большинство из них, за исключением таких вещей, как стереть или =).
и что утверждение в https://en.cppreference.com/w/cpp/container
- Элементы одного контейнера могут быть изменены одновременно с теми функциями-членами, которые не указаны для доступа к этим элементам.
Изменить: прошу прощения за отсутствие подробностей, вот более подробный код. Я стараюсь делать исключения, как предложил @Yksisarvinen, хотя я с этим не знаком, поэтому могут быть ошибки.
class myManager
{
public:
myManager() = default;
void insert(const std::string& id)
{
std::lock_guard lock(myMutex);
if (!myMap.contains(id))
{
myMap[id];// if throw exception here, then nothing happen?
try {
mySeparateMutex[id];
}
catch (...)
{
myMap.erase(myMap.find(id));// need to erase element with Key==id from myMap to consistent with mySeparateMutex
throw;
}
}
}
std::tuple get(const std::string& id, bool& ok)
{
std::lock_guard lock(myMutex);
static std::vector Empty;
static std::mutex noMutex;
ok = false;
if (myMap.contains(id))
{
ok = true;
return std::tie(myMap[id], mySeparateMutex[id]);// I think operator[] will throw nothing
}
return std::tie(Empty, noMutex);
}
private:
// assume we can only access directly to these private member by public member functions above
std::mutex myMutex;
std::unordered_map myMap;
std::unordered_map mySeparateMutex;
};
myManager instance;
void fun0(const std::string& id)
{
bool isContain = false;
auto [a, b] = instance.get(id, isContain);
if (isContain)
{
std::lock_guard lock(b);// this mutex for preventing writing/reading container of same key
// do some change
a.push_back(1);
a.push_back(2);
}
}
void fun1(const std::string& id)
{
instance.insert(id);
}
int fun2(const std::string& id, int index)
{
bool isContain = false;
auto [a, b] = instance.get(id, isContain);
if (isContain)
{
std::lock_guard lock(b);
if (index >= 0 && index < a.size())
{
// do some read
return a[index];
}
}
return -1;
}
int main()
{
fun1("apple");
std::thread a([]() {
fun0("apple");
});
std::thread b([]() {
fun1("apple");
});
std::thread c([]() {
fun2("apple", 0);
});
std::thread d([]() {
fun0("apple");
});
// there are some other threads call fun* with id=="banana",...
a.join();
b.join();
c.join();
d.join();
// join other threads
return 0;
}
Подробнее здесь: https://stackoverflow.com/questions/792 ... -container
Мобильная версия