Все остальные ответы здесь являются отличным описанием основной проблемы - особенно в отношении приоритетов потоков и готовых очередей. Однако следует учитывать и IO. Я говорю здесь только о Windows, так как это единственная платформа, которую я знаю с какими-либо полномочиями, но другие ядра, вероятно, будут иметь аналогичные проблемы.
В Windows, когда IO завершается, то, что называется APC в режиме ядра (асинхронный вызов процедуры), ставится в очередь против потока, который инициировал IO, чтобы завершить его. Если поток находится в ожидании объекта планировщика (например, семафора в вашем примере), то поток удаляется из очереди ожидания для этого объекта, что приводит к тому, что ожидание (внутренний режим ядра) завершается (что-то вроде) STATUS_ALERTED. Теперь, поскольку эти APC в режиме ядра являются подробностями реализации, и вы не можете видеть их в пользовательском режиме, реализация ядра WaitForMultipleObjects перезапускает ожидание в этой точке, что приводит к выталкиванию потока в конец очереди. С точки зрения режима ядра очередь по-прежнему находится в порядке FIFO, поскольку первый вызывающий API-интерфейс ожидания по-прежнему находится во главе очереди, однако, с вашей точки зрения, в пользовательском режиме вас просто подталкивают к задняя часть очереди из-за чего-то, что вы не видели и, возможно, не имели никакого контроля. Это заставляет порядок очереди казаться случайным из пользовательского режима. Реализация по-прежнему является простым FIFO, но из-за ввода-вывода она не похожа на более высокую степень абстракции.
Я предполагаю, что здесь немного больше, но я бы подумал, что Unix-подобные ОС имеют схожие ограничения по доставке сигналов и местам, где ядру необходимо перехватить процесс для запуска в его контексте.
Теперь это не всегда происходит, но документация должна быть консервативной, и если порядок явно не гарантированно будет FIFO (что, как описано выше - по крайней мере, для окон - не может быть), то порядок описан в документации как «случайный» или «недокументированный» или что-то еще, потому что случайный процесс управляет им. Это также дает поставщикам ОС возможность изменить порядок в будущем.