Этот код демонстрирует, что мьютекс распределяется между двумя потоками, но что-то странное происходит с блоком обзора вокруг thread_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);
usleep (10*1000); // or whatever
}
std::cerr << "#";
std::cerr.flush ();
}
});
while (true)
{
std::lock_guard <std::mutex> main_lock (m);
std::cerr << ".";
std::cerr.flush ();
}
}
Это в основном работает, как есть, но область видимости вокруг thread_lock
теоретически не должно быть необходимости. Однако, если вы закомментируете это …
#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);
usleep (10*1000); // or whatever
// }
std::cerr << "#";
std::cerr.flush ();
}
});
while (true)
{
std::lock_guard <std::mutex> main_lock (m);
std::cerr << ".";
std::cerr.flush ();
}
}
Вывод выглядит так:
........########################################################################################################################################################################################################################################################################################################################################################################################################################################################################################
то есть, похоже, что thread_lock
НИКОГДА не уступает main_lock
,
Почему thread_lock
всегда получить замок и main_lock
всегда ждать, если лишний блок обзора удаляется?
Я протестировал ваш код (с удаленной областью блоков) в Linux с GCC (7.3.0), используя pthreads, и получил те же результаты, что и вы. Основной поток истощен, хотя, если бы я подождал достаточно долго, я бы иногда видел, как основной поток выполняет какую-то работу.
Тем не менее, я запустил тот же код в Windows с MSVC (19.15), и ни один поток не был истощен.
Похоже, вы используете posix, так что я предполагаю, что ваша стандартная библиотека использует pthreads на серверной части? (Я должен связать pthreads даже с C ++ 11.) Мьютексы Pthreads не гарантируют справедливости. Но это только половина истории. Ваш вывод, похоже, связан с usleep
вызов.
Если я достану usleep
Я вижу справедливость (Linux):
// fair again
while (true)
{
std::lock_guard <std::mutex> thread_lock (m);
std::cerr << "#";
std::cerr.flush ();
}
Я предполагаю, что из-за долгого сна, в то время как удерживая мьютекс, практически гарантируется, что основной поток будет как заблокировано, так и заблокировано может быть. Представьте, что сначала главный поток может попытаться раскрутиться в надежде, что мьютекс скоро станет доступен. Через некоторое время он может попасть в список ожидания.
Во вспомогательной теме lock_guard
объект уничтожается в конце цикла, поэтому мьютекс освобождается. Он разбудит основной поток, но сразу создаст новый lock_guard
который снова блокирует мьютекс. Маловероятно, что основной поток захватит мьютекс, потому что он был запланирован. Поэтому, если в этом небольшом окне не произойдет переключение контекста, вспомогательный поток, вероятно, снова получит мьютекс.
В коде с блоком контекста мьютекс во вспомогательном потоке освобождается перед вызовом ввода-вывода. Печать на экран занимает много времени, поэтому у основного потока достаточно времени, чтобы получить возможность захватить мьютекс.
Как сказал @Ted Lyngmo в своем ответе, если вы добавите сон перед lock_guard
создается, это делает голод гораздо менее вероятным.
while (true)
{
usleep (1);
std::lock_guard <std::mutex> thread_lock (m);
usleep (10*1000);
std::cerr << "#";
std::cerr.flush ();
}
Я также попробовал это с yield, но мне нужно было 5+, чтобы сделать его более справедливым, что заставляет меня поверить, что есть и другие нюансы в фактических деталях реализации библиотеки, планировщике ОС и эффектах подсистемы кэширования и памяти.
Кстати, спасибо за отличный вопрос. Это было действительно легко проверить и поиграть.
Вы можете дать ему подсказку о перепланировании, сдавая потоки (или спя) без владения мьютексом. Довольно долгий сон ниже будет наверное заставить его вывести #. #. #. #. в совершенстве. Если вы переключитесь на урожайность, вы, вероятно, получите блоки из ############ ……………, но примерно 50/50 в долгосрочной перспективе.
#include <thread>
#include <mutex>
#include <iostream>
#include <unistd.h>
int main ()
{
std::mutex m;
std::thread t ([&] ()
{
while (true)
{
usleep (10000);
//std::this_thread::yield();
std::lock_guard <std::mutex> thread_lock (m);
std::cerr << "#" << std::flush;
}
});
while (true)
{
usleep (10000);
//std::this_thread::yield();
std::lock_guard <std::mutex> main_lock (m);
std::cerr << "." << std::flush;
}
}