Большая загрузка процессора с использованием std::lock (C++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, 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();
    }

4 ответа

Решение

На моей машине следующий код печатается 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 или чрезмерное использование процессора.

Обновить

Если эта программа использует чрезмерный процессор на вашей платформе, попробуйте изменить его на вызов ::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

Как сказано в документации, [t] объекты блокируются неопределенной серией вызовов lock, try_lock, unlock. Просто нет способа, который мог бы быть эффективным, если мьютексы удерживались другими потоками в течение значительного периода времени. Функция не может ждать без вращения.

std::lock() функция, не являющаяся членом, может вызвать проблемы с Live-Lock или снижение производительности, она гарантирует только "Never Dead-Lock".

Если вы можете определить "Порядок блокировки (Lock иерархия)" нескольких мьютексов по дизайну, желательно не использовать универсальный std::lock() но блокируйте каждый мьютекс в заранее определенном порядке.

Обратитесь к Получение нескольких блокировок без тупика для более подробной информации.

Сначала я хочу поблагодарить за все ответы.

Во время работы над примером кода, который воспроизводит эффект, я нашел источник проблем.

Условная часть блокирует оба мьютекса, в то время как для std::condition_variable::wait() функция.

Но мне все еще интересно, что происходит за кулисами, что приводит к такой высокой загрузке процессора.

Другие вопросы по тегам