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