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