Грокаем C++
9.36K subscribers
44 photos
1 video
3 files
567 links
Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов.

По всем вопросам (+ реклама) @ninjatelegramm

Менеджер: @Spiral_Yuri
Реклама: https://telega.in/c/grokaemcpp
Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
Download Telegram
Порядок взятия замков. Ч1
#опытным

В этом посте я намеренно совершил одну ошибку, как байт на комменты и следующие посты. Но вы как-то пропустили ее, хотя и разбирали ту же самую тему в комментариях.

Cтандартное решение этой проблемы дедлока из постов выше - лочить замки в одном и том же порядке во всех потоках.


На самом деле залочивание мьютексов в одном и том же порядке - не общепринятая концепция решения проблемы блокировки множества замков. Это лишь одна из стратегий. И она не используется в стандартной библиотеке!

Когда-то у меня тоже была уверенность, что std::scoped_lock блочит мьютексы в порядоке их адресов. Условно, в начале лочим замок с меньшим адресом. Потом с большим и так далее.

Но как я и написал в середине того же поста, что std::scoped_lock вообще не гарантирует никакого порядка залочивания. Гарантируется только что такой порядок не может привести к дедлоку.

Давайте посмотрим на следующий пример:

std::mutex log_mutex;

struct MyLock {
MyLock() = default;

void lock() {
mtx.lock();
std::lock_guard lg{log_mutex};
std::cout << "Lock at address " << &mtx << " is acquired." << std::endl;
}

bool try_lock() {
auto result = mtx.try_lock();
std::lock_guard lg{log_mutex};
std::cout << std::this_thread::get_id() << " Try lock at address " << &mtx << ". " << (result ? "Success" : "Failed") << std::endl;
return result;
}

void unlock() {
mtx.unlock();
std::lock_guard lg{log_mutex};
std::cout << "Lock at address " << &mtx << " is released." << std::endl;
}

private:
std::mutex mtx;
};

MyLock lock1;
MyLock lock2;
MyLock lock3;

constexpr size_t iteration_count = 100;

void func_thread1() {
size_t i = 0;
while(i++ < iteration_count) {
{
std::lock_guard lg{log_mutex};
std::cout << std::endl << std::this_thread::get_id() << " Start acquiring thread1" << std::endl << std::endl;
}
std::scoped_lock scl{lock1, lock2, lock3};
std::lock_guard lg{log_mutex};
std::cout << std::endl << std::this_thread::get_id() << " End acquiring thread1" << std::endl << std::endl;
}
}

void func_thread2() {
size_t i = 0;
while(i++ < iteration_count) {
{
std::lock_guard lg{log_mutex};
std::cout << std::endl << std::this_thread::get_id() << " Start acquiring thread2" << std::endl << std::endl;
}
std::scoped_lock scl{lock3, lock2, lock1};
std::lock_guard lg{log_mutex};
std::cout << std::endl << std::this_thread::get_id() << " End acquiring thread2" << std::endl << std::endl;
}
}

int main() {
std::jthread thr1{func_thread1};
std::jthread thr2{func_thread2};
}


Все довольно просто. Определяем класс-обертку вокруг std::mutex, который позволит нам логировать все операции с ним, указывая идентификатор потока. Определяем все методы, включая try_lock, чтобы MyLock можно было использовать с std::scoped_lock.

Также определяем 2 функции, которые будут запускаться в разных потоках и пытаться локнуть сразу 3 замка. И блокируют они их в разных порядках. Все это в циклах, чтобы какую-то статистику иметь. С потоками сложно детерминировано общаться.

Запускаем это дело и смотрим на вывод консоли. Там будет огромное полотно текста, но вы сможете заметить в нем "несостыковочку" с теорией про блокировку по адресам. Возможный кусочек вывода:

129777453237824 Start acquiring thread1

129777453237824 Lock at address 0x595886d571e0 is acquired.
129777453237824 Try lock at address 0x595886d57220
129777453237824 Try lock at address 0x595886d57260
...
129777442752064 Start acquiring thread2

129777442752064 Lock at address 0x595886d57260 is acquired.
129777442752064 Try lock at address 0x595886d57220
129777442752064 Try lock at address 0x595886d571e0


Тут наглядно показано, что мьютексы лочатся в противоположном порядке в разных потоках. И в коде мьютексы передаются в скоупд лок в противоположном порядке. А значит дело тут не в адресах, а в чем-то другом. О этом в следующий раз.

Don't get fooled. Stay cool.

#concurrency #cpp17
🔥11👍75😱3