34

我最近实现线程/互斥锁管理器的努力最终导致 75% 的 CPU 负载(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_variablewithwait()notify_one()onmutex1来同时有选择地执行某些代码。

简单的改变

std::unique_lock<std::mutex> lock1( mutex1 );
std::unique_lock<std::mutex> lock2( mutex2 );

将 CPU 使用率降至正常的 1-2%。

我不敢相信,这个std::lock()功能是那么低效。这可能是 g++ 4.6.3 中的错误吗?

编辑:(示例)

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>

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();
    }
4

4 回答 4

28

在我的机器上,以下代码每秒打印 10 次并消耗几乎 0 cpu,因为大多数时间线程要么处于睡眠状态,要么被锁定的互斥锁阻塞:

#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 上运行它,活动监视器应用程序说这个进程正在使用 0.1% 的 cpu。该机器是 Intel Core i5(4 核)。

我很乐意以任何方式调整此实验以尝试创建活锁或过多的 CPU 使用率。

更新

如果此程序在您的平台上使用过多的 CPU,请尝试将其更改为 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

于 2013-01-25T15:37:55.497 回答
6

std::lock()成员函数可能会导致 Live-lock 问题或性能下降,它只保证“ Never Dead-lock ”。

如果您可以通过设计确定多个互斥锁的“锁定顺序(锁定层次结构)”,则最好不要使用泛型std::lock(),而是按预先确定的顺序锁定每个互斥锁。

有关更多详细信息,请参阅在没有死锁的情况下获取多个锁。

于 2013-01-25T03:18:54.307 回答
6

正如文档所说,[t] 对象被一系列未指定的 lock、try_lock、unlock 调用锁定。如果互斥锁被其他线程持有很长一段时间,那么根本不可能有效率。如果不旋转,函数就无法等待。

于 2012-12-02T09:14:07.077 回答
1

首先,我要感谢所有答案。

在重现效果的示例代码的工作中,我发现了问题的根源。

条件部分锁定两个互斥锁,而它只使用一个用于该std::condition_variable::wait()功能。

但我仍然想知道,幕后发生了什么,会产生如此高的 CPU 负载。

于 2012-12-03T04:09:17.760 回答