Есть ли потенциальная проблема в этом фрагмент кода?
#include <mutex>
#include <map>
#include <vector>
#include <thread>
constexpr int FOO_NUM = 5;
int main()
{
std::map<int, std::mutex> mp;
std::vector<std::thread> vec;
for(int i=0; i<2; i++)
{
vec.push_back(std::thread([&mp](){
std::lock_guard<std::mutex> lk(mp[FOO_NUM]); //I think there is some potential problem here, am I right?
//do something
}));
}
for(auto& thread:vec)
{
thread.join();
}
Согласно документ, в котором говорится, что:
Inserts value_type(key, T()) if the key does not exist. This function is equivalent to return insert(std::make_pair(key, T())).first->second;
Я думаю, что в вышеупомянутом фрагменте кода есть потенциальная проблема. Вы видите, что это может произойти:
1. первый поток создал mutex
и блокирует mutex
.
2. второй поток создал новый, а mutex
, созданный в первом потоке, необходимо уничтожить, пока он все еще используется в первом потоке.
operator[]
ассоциативных и неупорядоченных контейнеров не указано, что они защищены от гонок данных, если они вызываются без синхронизации в нескольких потоках. См. [container.requirements.dataraces]/1.
Поэтому ваш код имеет гонку данных и, следовательно, неопределенное поведение. Не имеет значения, создан ли новый мьютекс.
Да, существует гонка данных, но она еще более фундаментальна.
Ни один из контейнеров в библиотеке C++ никоим образом не является потокобезопасным. Ни один из их операторов не является потокобезопасным.
mp[FOO_NUM]
В показанном коде несколько потоков выполнения вызывают оператор карты []
. Это не потокобезопасный, сам оператор. То, что содержится на карте, не имеет значения.
the second thread created a new one, and the mutex created in the first thread needs to be destroyed while it's still used in the first thread.
Единственное, что уничтожает любой мьютекс в показанном коде, — это деструктор карты, когда сама карта уничтожается при возврате из main()
.
std::lock_guard<std::mutex>
уничтожает ли нет свой мьютекс, когда std::lock_guard
уничтожается и освобождает мьютекс, конечно. Вызов исполняющим потоком оператора []
карты может создать по умолчанию новый std::mutex
, но нет ничего, что могло бы разрушить его при присоединении к потоку выполнения. Значение, созданное по умолчанию в std::map
его оператором []
, уничтожается только тогда, когда что-то явно уничтожает его.
И сам оператор []
не является потокобезопасным, он не имеет ничего общего с созданием или уничтожением мьютекса.
std::map<int, std::mutex> mp;
как пустой. И они оба пытаются создать mutex
один за другим, поэтому, когда второй поток вставляет std::pair<int, mutex>
в mp
, mutex
, уже созданный первым потоком, необходимо уничтожить. Поэтому я не согласен с вами насчет того, когда mutex
уничтожается.
Нет, на данный момент это уже неопределенное поведение, поскольку оператор []
не возвращает значение до тех пор, пока новое значение не будет построено по умолчанию (если указанный ключ не существовал до вызова оператора []
). Поэтому для этого требуется одновременный вызов оператора []
из нескольких потоков выполнения. Неопределенное поведение. И если одному потоку выполнения удается создать конструкцию по умолчанию и вернуться из оператора []
, второму потоку выполнения не нужно ничего создавать (или уничтожать).
Константная функция-член контейнеров сохраняет поток.
Примечание.
-fsanitize=thread
(дезинфицирующее средство для нитей) может помочь вам обнаружить большинство условий гонки: пример божественной стрелы. Как правило, эмпирическое правило: чтение данных из нескольких потоков, как правило, нормально, но если какая-либо операция приведет к модификации, вам придется синхронизировать все задействованные потоки (также читатели).operator[]
изstd::map
является потокобезопасным только в том случае, если элемент уже существует глянь сюда) - ваш код будет в порядке, если вы создадите мьютекс перед потоками пример