Что происходит, когда два потока пытаются выполнить try_lock () на одном и том же мьютексе в то же время? - PullRequest
0 голосов
/ 04 июля 2019

Это мой второй пост. Мой первый пост был в качестве старшекурсника от вступления до класса C. Я надеюсь, что в этот раз мне будет лучше.

Я разрабатываю стохастическую модель для своих исследований и ищу способы повышения эффективности вычислений в моих алгоритмах. Недавно коллега познакомил меня с многопоточностью, и я успешно реализовал ее в нескольких моих алгоритмах, не требующих блокировок ...

Теперь я пытаюсь изменить / проделать алгоритм, который потребует блокировок. В своей литературе / поисках StackOverflow / Google я довольно много узнал о классе мьютекса. Однако проблема, с которой я сталкиваюсь, кажется уникальной. Для контекста самое близкое, что я мог найти к ответу, было здесь:

Что происходит, когда два потока пытаются заблокировать один и тот же ресурс в одно и то же время?

По-прежнему не было прямого ответа на вопрос, который я собираюсь задать.

Я пытался контролировать доступ к памяти между потоками, используя динамически распределенный массив указателей на объекты мьютекса. Мой код компилируется без предупреждений / ошибок (скомпилировано с флагами -pthread -std = c ++ 11) и прекрасно работает вплоть до этого многопоточного алгоритма, в котором генерируется Segfault. После исследования с помощью lldb я обнаружил, что выбрасываемое исключение выглядит следующим образом:

Скриншот вывода lldb

После дальнейшего изучения я обнаружил, что все потоки, на которые ссылается вывод lldb, пытаются try_lock () для объекта мьютекса EXACT SAME, поскольку массив указателей, содержащий адрес этого объекта, был передан каждому потоку.

Мой вопрос, который похож на ранее упомянутый пост:

Что происходит, когда попытка try_lock () выполняется несколькими потоками (более одного) на одном и том же мьютексе в ТОЧНОЕ ЖЕ время (один и тот же тактовый такт процессора)? Есть ли у новых реализаций класса mutex обходные пути для этого, казалось бы, катастрофического события (т.е. shard_mutex, timed_mutex и т. Д.)?

Это недавно потрясло мой разум, так что любая проницательность будет принята с благодарностью. Большой привет сообществу S / O за всю помощь, на этом посте и на всех других, которые были неоценимы для моего роста как программиста.

ССЫЛКА НА КОД:

https://github.com/tylerbalbright/StackOverflow_7_4.git

Ошибка в файле RVEdata.cpp в строке 751 или 857.

ИСПРАВЛЕНО, НО НЕ РЕШЕНО:

Мне удалось исправить мой код, используя деку объектов мьютекса, в отличие от создания вектора указателей на динамически создаваемые мьютексы. Решение было предложено другими пользователями здесь:

Как я могу использовать что-то вроде std :: vector ?

В моем первом (неудачном) испытании я пытался создать массив указателей на мьютексы, например:

long int N = RuntimeVector.size(); //Varying size at runtime
std::mutex *MutexPtrs;
MutexPtrs = new std::mutex[N];

Затем я передал бы вновь созданный массив в функцию в качестве указателя, который передал бы указатель на массив в новый поток, например так:

void SomeFunction(std::mutex *PosLocks[])
{
.
..
...

    SearchersPos.push_back(std::async(std::launch::async, &RVEdata::PositiveSearch, this, PosLocks));
}

Используя этот метод, код не завершался ошибкой при КАЖДОМ выполнении, но он завершался с ошибкой примерно в 90% случаев с EXC_BAD_ACCESS. Что интересно, так это то, что при КАЖДОМ неудачном выполнении был получен неправильный доступ, когда несколько потоков пытались попытаться заблокировать один и тот же мьютекс. Это НИКОГДА не завершалось неудачей, когда только один поток пытался выполнить try_lock на одиночном мьютексе. Когда я запускал тот же код на нашем HPC, сбои происходили примерно в 95% случаев, но я не мог найти столько информации в gdb, сколько мог бы в lldb (я менее опытен в командной строке gdb).

PS - я работаю на macOS High Sierra 10.13.6, Apple LLVM версии 10.0.0 (clang-1000.11.45.5).

Ответы [ 2 ]

2 голосов
/ 04 июля 2019

Когда несколько потоков одновременно блокируют попытку, один из них блокируется, а остальные перестают работать.

Ваша принцесса ошибка в другом замке вызвано чем-то другим.

0 голосов
/ 05 июля 2019

Что происходит, когда попытка try_lock () выполняется несколькими потоками (более одного) на одном и том же мьютексе в ТОЧНОЕ ЖЕ время (один и тот же тактовый такт процессора)? Есть ли в новых реализациях класса mutex обходные пути для этого, казалось бы, катастрофического события (например, shard_mutex, timed_mutex и т. Д.)?

В этом нет абсолютно ничего катастрофического, это то, что мьютексы для . Вы не нашли изъян в том, как работают мьютексы, select не сломан. Вам следует прочитать Первое правило программирования от одного из основателей StackOverflow.

В моем первом (неудачном) испытании я пытался создать массив указателей на мьютексы, например:

Проблема в том, что вы создаете два массива мьютексов :

std::mutex *PosLocks;
std::mutex *GndLocks;

PosLocks = new std::mutex[N];
GndLocks = new std::mutex[N];

Но затем вы передаете адреса массивов :

NetExists = FindNetwork(N, &PosLocks, &GndLocks);

который вызывает эту функцию:

bool RVEdata::FindNetwork(long int N, std::mutex *PosLocks[], std::mutex *GndLocks[])

Итак, теперь вы передали std::mutex** аргументы функции, которая может быть либо указателем на массив мьютексов , либо массивом указателей на мьютексы.

И затем вы получаете доступ к ним в виде массива mutex* вместо указателя на mutex[]:

    if (PosLocks[i]->try_lock() == true)

PosLocks[i] индексирует в массив для получения mutex*, а затем использует оператор -> для разыменования указателя. Но это задом наперед! У вас нет массива mutex*!

Как я уже сказал в моем комментарии выше, вы просто неправильно обращаетесь к массиву. Это означает, что программа пытается заблокировать объект mutex по какому-либо адресу, где нет объекта mutex, потому что вы читаете адрес, который находится где-то в середине объекта мьютекса, а не в его начале.

В строке выше следует сначала разыменовать указатель, чтобы получить индекс mutex[], а затем , а затем в массив:

    if ((*PosLocks)[i].try_lock() == true)

Или даже лучше было бы просто прекратить передавать указатель на массив в первую очередь, то есть объявить функцию как:

bool RVEdata::FindNetwork(long int N, std::mutex PosLocks[], std::mutex GndLocks[])

и назовите его так:

NetExists = FindNetwork(N, PosLocks, GndLocks);

Теперь вы можете просто получить доступ к массиву напрямую:

    if (PosLocks[i].try_lock() == true)

Еще лучше прекратить использование динамически создаваемых массивов:

std::vector<std::mutex> PosLocks;
std::vector<std::mutex> GndLocks;
// ...
NetExists = FindNetwork(N, PosLocks, GndLocks);
// ...

bool RVEdata::FindNetwork(long int N, std::vector<std::mutex>& PosLocks, std::vector<std::mutex>& GndLocks)
{
    // ...
    if (PosLocks[i].try_lock() == true)

Эта проблема не возникла бы, если бы вы не смешивали массивы и указатели с динамическим размещением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...