使用std :: lock的大量CPU负载(c ++ 11)

mac*_*mac 31 c++ mutex c++11 stdthread

我最近实现线程/互斥管理器的努力最终导致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 );
Run Code Online (Sandbox Code Playgroud)

该类的另一部分使用std::condition_variablewith wait()notify_one()on mutex1来同时有选择地执行某些代码.

简单的改变

std::unique_lock<std::mutex> lock1( mutex1 );
std::unique_lock<std::mutex> lock2( mutex2 );
Run Code Online (Sandbox Code Playgroud)

使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();
    }
Run Code Online (Sandbox Code Playgroud)

How*_*ant 26

在我的机器上,以下代码每秒打印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();
}
Run Code Online (Sandbox Code Playgroud)

样本输出:

f1 has the two locks
f2 has the two locks
f1 has the two locks
...
Run Code Online (Sandbox Code Playgroud)

我在OS X上运行它,并且Activity Monitor应用程序说这个过程使用0.1%cpu.该机器是英特尔酷睿i5(4核).

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

更新

如果此程序在您的平台上使用过多的CPU,请尝试将其更改为调用::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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我很想知道这对你有什么影响,谢谢.

更新2

经过长时间的拖延,我写了一篇关于这个主题的论文初稿.本文比较了完成这项工作的4种不同方式.它包含可以复制并粘贴到您自己的代码中并自行测试的软件(请报告您找到的内容!):

http://howardhinnant.github.io/dining_philosophers.html

  • @StackedCrooked:`unique_lock :: release()`函数释放锁的所有权.它不会解锁互斥锁.所以本地`u0`负责锁定`l0`,而*if*`l1.try_lock()`抛出或返回false,`u0`负责解锁`l0`.如果`l1.try_lock()`成功,现在'l0`和`l1`都被锁定了.任务完成.剩下要做的就是告诉`u0`它不再对'l0`负责.调用客户端现在负责`l0`和`l1`. (4认同)
  • 使用更新的`:: lock()`函数将CPU使用率降低到可接受的值. (2认同)

Dav*_*rtz 6

如文档所述,对象是通过一系列未指定的锁定,try_lock和unlock的调用来锁定的。如果互斥锁在相当长的一段时间内被其他线程保持,则根本没有办法可能会有效。该函数无法等待而不会旋转。

  • @David Schwartz:如果失败后既有收益又有尝试锁定/阻止先前失败的try_lock的互斥锁,则旋转可能非常缓慢且不占用CPU。这是一个有效的开源实现:http://llvm.org/svn/llvm-project/libcxx/trunk/include/mutex。已经在高竞争场景中测试了此实现,并且发现该实现非常有效。您回答的前提是错误的。如果线程B拥有线程A需要的任何锁,则操作系统将驻留线程A,其方式与我们仅在谈论单个互斥锁的方式完全相同。 (7认同)
  • 我所知道的所有现代OS都将在线程未准备好运行时(因为它被锁定的互斥锁阻塞)而停放一个线程。这就是该算法的一个不错的实现。我给了您一个链接,以查看源代码。 (3认同)
  • 尝试进行设置。查看是否可以创建实时锁定。 (2认同)

yoh*_*hjp 6

std::lock()非成员函数可能导致活锁问题或性能下降,它保证只有" 永生不灭锁 ".

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

有关更多详细信息,请参阅获取无死锁的多个锁.