Массивная загрузка процессора с использованием std:: lock (С++ 11)
Мои недавние усилия по реализации диспетчера потоков/мьютексов закончились загрузкой процессора 75% (4 ядра), в то время как все четыре работающих потока либо находились в спящем режиме, либо ожидали разблокировки мьютекса.
Определенный класс слишком велик для того, чтобы его можно было разместить здесь целиком, но я мог бы сузить причину до безопасного зацепления двух мьютексов
std::unique_lock<std::mutex> lock1( mutex1, std::defer_lock );
std::unique_lock<std::mutex> lock2( mutex2, std::defer_lock );
std::lock( lock1, lock2 );
Другая часть класса использует std::condition_variable
с wait()
и notify_one()
на mutex1
для некоторого кода, который будет выполняться выборочно одновременно.
Простое изменение на
std::unique_lock<std::mutex> lock1( mutex1 );
std::unique_lock<std::mutex> lock2( mutex2 );
довел загрузку процессора до нормального уровня 1-2%.
Не могу поверить, что функция std::lock()
неэффективна. Может ли это быть ошибкой в g++ 4.6.3?
редактировать: (пример)
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
#include <condition_variable>
std::mutex mutex1, mutex2;
std::condition_variable cond_var;
bool cond = false;
std::atomic<bool>done{false};
using namespace std::chrono_literals;
void Take_Locks()
{
while( !done )
{
std::this_thread::sleep_for( 1s );
std::unique_lock<std::mutex> lock1( mutex1, std::defer_lock );
std::unique_lock<std::mutex> lock2( mutex2, std::defer_lock );
std::lock( lock1, lock2 );
std::this_thread::sleep_for( 1s );
lock1.unlock();
lock2.unlock();
}
}
void Conditional_Code()
{
std::unique_lock<std::mutex> lock1( mutex1, std::defer_lock );
std::unique_lock<std::mutex> lock2( mutex2, std::defer_lock );
std::lock( lock1, lock2 );
std::cout << "t4: waiting \n";
while( !cond )
cond_var.wait( lock1 );
std::cout << "t4: condition met \n";
}
int main()
{
std::thread t1( Take_Locks ), t2( Take_Locks ), t3( Take_Locks );
std::thread t4( Conditional_Code );
std::cout << "threads started \n";
std::this_thread::sleep_for( 10s );
std::unique_lock<std::mutex> lock1( mutex1 );
std::cout << "mutex1 locked \n" ;
std::this_thread::sleep_for( 5s );
std::cout << "setting condition/notify \n";
cond = true;
cond_var.notify_one();
std::this_thread::sleep_for( 5s );
lock1.unlock();
std::cout << "mutex1 unlocked \n";
std::this_thread::sleep_for( 6s );
done = true;
t4.join(); t3.join(); t2.join(); t1.join();
}
Ответы
Ответ 1
На моей машине следующий код печатается 10 раз в секунду и потребляет почти 0 процессоров, потому что большую часть времени поток либо спит, либо блокируется на заблокированном мьютексе:
#include <chrono>
#include <thread>
#include <mutex>
#include <iostream>
using namespace std::chrono_literals;
std::mutex m1;
std::mutex m2;
void
f1()
{
while (true)
{
std::unique_lock<std::mutex> l1(m1, std::defer_lock);
std::unique_lock<std::mutex> l2(m2, std::defer_lock);
std::lock(l1, l2);
std::cout << "f1 has the two locks\n";
std::this_thread::sleep_for(100ms);
}
}
void
f2()
{
while (true)
{
std::unique_lock<std::mutex> l2(m2, std::defer_lock);
std::unique_lock<std::mutex> l1(m1, std::defer_lock);
std::lock(l2, l1);
std::cout << "f2 has the two locks\n";
std::this_thread::sleep_for(100ms);
}
}
int main()
{
std::thread t1(f1);
std::thread t2(f2);
t1.join();
t2.join();
}
Образец вывода:
f1 has the two locks
f2 has the two locks
f1 has the two locks
...
Я запускаю это на OS X, и приложение Activity Monitor говорит, что этот процесс использует 0,1% ЦП. Машина представляет собой Intel Core i5 (4 ядра).
Я рад настроить этот эксперимент любым способом, чтобы попытаться создать live-lock или чрезмерное использование процессора.
Обновить
Если эта программа использует чрезмерную загрузку ЦП на вашей платформе, попробуйте изменить ее на call ::lock()
, где это определяется с помощью:
template <class L0, class L1>
void
lock(L0& l0, L1& l1)
{
while (true)
{
{
std::unique_lock<L0> u0(l0);
if (l1.try_lock())
{
u0.release();
break;
}
}
std::this_thread::yield();
{
std::unique_lock<L1> u1(l1);
if (l0.try_lock())
{
u1.release();
break;
}
}
std::this_thread::yield();
}
}
Мне было бы интересно узнать, имеет ли это какое-то значение для вас, спасибо.
Обновление 2
После долгой задержки я написал первый черновик статьи на эту тему. В статье сравниваются 4 различных способа выполнения этой работы. Он содержит программное обеспечение, которое вы можете скопировать и вставить в свой собственный код и протестировать себя (и, пожалуйста, сообщите, что вы нашли!):
http://howardhinnant.github.io/dining_philosophers.html
Ответ 2
Функция non-member std::lock()
может вызвать проблему с Live-lock или ухудшение производительности, она гарантирует только "Never Dead-lock".
Если вы можете определить "порядок блокировки (блокировка иерархии)" нескольких мьютексов по дизайну, предпочтительнее не использовать общий std::lock()
, а блокировать каждый мьютекс в заранее определенном порядке.
Подробнее см. Приобретение нескольких замков без тупика.
Ответ 3
Как говорится в документации, [t] объекты закрываются неуказанной серией вызовов блокировки, try_lock, unlock. Просто нет способа, который может быть эффективным, если мьютексы удерживаются другими потоками в течение значительного периода времени. Функция не может ждать без вращения.
Ответ 4
Сначала я хочу поблагодарить за все ответы.
Во время работы над примером кода, который воспроизводит эффект, я нашел источник проблем.
Условная часть блокирует оба мьютекса, а для функции std::condition_variable::wait()
используется только одна.
Но я все еще удивляюсь, что происходит за сценой, которая производит такую высокую загрузку процессора.