Пишу для Хабра серию из трех статей про горутины и по дороге выкидываю из нее целые куски. Механизм интересный, а главу уводит в сторону, вот и режу саму статью. Складывать их некуда, так что буду выкладывать сюда. Первые два, оба проверяются за пять минут.
Первое. Когда готовы сразу несколько каналов,
select берет не первый по порядку. Он строит pollorder, случайную перестановку кейсов select.go:191, и идет уже по ней. Три всегда готовых канала, 300 тысяч итераций:case 1 (в исходнике 1-й): 100216 33.4%
case 2 (в исходнике 2-й): 99812 33.3%
case 3 (в исходнике 3-й): 99972 33.3%
Ровно по трети. Без перестановки первый case побеждал бы всегда, а остальные голодали.
Рядом живет второй порядок обхода, совсем про другое:
lockorder select.go:206 сортирует каналы по адресу hchan. Это защита от дедлока, когда два select работают на одном наборе каналов. Классическое "бери локи в одном порядке", только внутри рантайма.Второе.
sync.Mutex умеет голодать. Если горутина прождала замок дольше миллисекунды (starvationThresholdNs = 1e6, internal/sync/mutex.go:55, мьютекс переходит в режим голодания и дальше вручает замок первому в очереди напрямую, без розыгрыша.Восемь горутин дерутся за один замок, критическая секция 30 мкс, я меряю собственное ожидание:
среднее ожидание: 125.855µs
максимум ожидания: 1.076358ms
макс. обгонов: 34
Максимум в двух прогонах подряд встал на 1.076 и 1.079 мс. Это не случайность, это тот самый порог: как только ожидание переваливает за миллисекунду, обгонять больше не дают.
Код обоих замеров лежит в репозитории, каталоги select_fair и mutex_starve.
Деталь, которая удивила меня сильнее всего. Очередь ждущих живет не в мьютексе. В нем 8 байт, очередь туда не влезет физически. Все ждущие лежат в глобальной таблице семафоров рантайма: 251 корзина, в каждой сбалансированное дерево sema.go:49. То есть твой мьютекс не хранит своих ждущих, он хранит только число, а очередь ему подбирают по хешу адреса...
