Почему std :: mutex занимает очень много времени? - PullRequest
0 голосов
/ 30 октября 2018

Этот код демонстрирует, что мьютекс распределяется между двумя потоками, но один поток имеет его почти все время.

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

Скомпилировано с g ++ 7.3.0 в Ubuntu 18.04 4.15.0-23-generic.

Вывод представляет собой сочетание символов # и ., показывающих, что мьютекс используется совместно, но картина удивительна. Как правило, что-то вроде этого:

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

т.е.. thread_lock блокирует мьютекс на очень долгое время. Через несколько или даже десятки секунд main_lock получает управление (кратко), затем thread_lock возвращает его и хранит его целую вечность. Звонок std::this_thread::yield() ничего не меняет.

Почему два мьютекса не могут одинаково получить блокировку, и как я могу сделать так, чтобы мьютекс был распределен сбалансированным образом?

Ответы [ 2 ]

0 голосов
/ 30 октября 2018

Создание 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-смысле (или даже омега-справедливым), где, если ожидается до N элементов, они все получат свой ход, прежде чем освобождающий поток получит еще один шанс.

0 голосов
/ 30 октября 2018

std::mutex не рассчитан на честность. Это не гарантирует, что порядок блокировки сохранен, вам либо повезло получить блокировку, либо нет.

Если вы хотите большей справедливости, рассмотрите возможность использования std::condition_variable , например, :

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

#include <unistd.h>

int main ()
{
    std::mutex m;
    std::condition_variable cv;
    std::thread t ([&] ()
    {
        while (true)
        {
            std::unique_lock<std::mutex> lk(m);
            std::cerr << "#";
            std::cerr.flush ();
            cv.notify_one();
            cv.wait(lk);
        }
    });

    while (true)
    {
        std::unique_lock<std::mutex> lk(m);
        std::cerr << ".";
        std::cerr.flush ();
        cv.notify_one();
        cv.wait(lk);
    }
}
...