首页 > 解决方案 > 为什么 std::mutex 需要很长时间才能共享?

问题描述

这段代码演示了互斥锁在两个线程之间共享,但一个线程几乎一直拥有它。

#include <thread>
#include <mutex>
#include <iostream>

#include <unistd.h>

int main ()
{
    std::mutex m;

    std::thread t ([&] ()
    {
        while (true)
        {
            {
                std::lock_guard <std::mutex> thread_lock (m);

                sleep (1); // or whatever
            }
            std::cerr << "#";
            std::cerr.flush ();
        }
    });

    while (true)
    {
        std::lock_guard <std::mutex> main_lock (m);
        std::cerr << ".";
        std::cerr.flush ();
    }
}

在 Ubuntu 18.04 4.15.0-23-generic 上使用 g++ 7.3.0 编译。

输出是#.字符的混合,表明互斥体正在共享,但模式令人惊讶。通常是这样的:

.......#####..........................##################......................##

thread_lock锁定互斥锁长时间。几秒甚至几十秒后,接收方main_lock(短暂地)接收控制权,然后thread_lock将其取回并保留很长时间。打电话std::this_thread::yield()不会改变任何事情。

为什么两个互斥锁获得锁定的可能性不同,如何使互斥锁以平衡的方式共享?

标签: c++pthreadsstdmutexstdmutex

解决方案


使std::mutex公平是有代价的。而在 C++ 中,你不需要为你不要求的东西付费。

您可以编写一个锁定对象,其中释放锁定的一方不能是下一个获得它的一方。更高级的是,您可以编写一个仅在其他人等待时才会发生的情况。

这是对公平互斥体的快速、未经测试的尝试:

struct fair_mutex {
  void lock() {
    auto l = internal_lock();
    lock(l);
  }
  void unlock() {
    auto l = internal_lock();
    in_use = false;
    if (waiting != 0) {
      loser=std::this_thread::get_id();
    } else {
      loser = {};
    }
    cv.notify_one();
  }
  bool try_lock() {
    auto l = internal_lock();
    if (in_use) return false;
    lock(l);
    return true;
  }
private:
  void lock(std::unique_lock<std::mutex>&l) {
    ++waiting;
    cv.wait( l, [&]{ return !in_use && std::this_thread::get_id() != loser; } );
    in_use = true;
    --waiting;
  }
  std::unique_lock<std::mutex> internal_lock() const {
    return std::unique_lock<std::mutex>(m);
  }
  mutable std::mutex m;
  std::condition_variable cv;
  std::thread::id loser;
  bool in_use = false;
  std::size_t waiting = 0;
};

这是“公平的”,因为如果您有两个线程争夺资源,它们将轮流进行。如果有人在等待一把锁,那么任何放弃锁的人都不会再次抓住它。

然而,这是线程代码。所以我可能会读一遍,但我不相信我第一次尝试写任何东西。

您可以将此(以增加成本)扩展为 n-way fair(甚至 omega-fair),如果有多达 N 个元素在等待,它们都会在释放线程获得另一个机会之前轮到它们。


推荐阅读