Голанг-методы, которые дадут гортаны

Насколько я понимаю, goroutines блокирует запуск других goroutines, если они слишком заняты. Для меня это означает, что производительность и отзывчивость моего приложения, вероятно, будут зависеть от меня, зная, какие методы библиотеки будут обеспечивать контроль над другими goroutines (например, обычно Read() и Write())

Есть ли какой-либо способ, который я могу точно знать, как различные методы библиотеки будут получать контроль над другими goroutines, то есть фактически не блокировать?

Есть ли способ реализовать новый метод, который вызывает сторонний код (включая асинхронный Win32 API, такой как findnextchangenotification, который полагается на waitforsingleobject или waitformultipleobjects) и ведет себя "хорошо" с планировщиком Go? В этом конкретном примере syscall будет сигнализировать, как только закончите, и мне нужно подождать, пока оно не закончится, и не исчерпайте всех остальных goroutines.

Есть ли еще одна "лучшая практика" для того, как бороться с операциями блокировки третьей стороны в Go, чтобы они не исчерпывали другие горуты?

Я предполагаю, что время выполнения Go может иметь какой-то IO-цикл, встроенный в фоновый поток, чтобы "приостановить" блокировку операций goroutine до тех пор, пока они не закончили с IO. Если это действительно так, то, я думаю, было бы полезно иметь возможность строить это для новых операций блокировки.

Ответ 1

Планировщик Go приостанавливает goroutines, которые ждут syscall и разбудят их, когда syscall завершит работу, предоставив вам синхронный API для работы с асинхронными вызовами.

Подробнее о том, как работает планировщик.

Тем не менее, нет точного способа определить, какой из goroutine будет пробужден за другим или получить управление от одного goroutine непосредственно другому, - это задание планировщика.

Ваша забота - это решительная проблема в Go, и вам не нужно беспокоиться об этом - код прочь!

Edit:

Дальнейшее уточнение; вы не должны кодировать, чтобы соответствовать (или лучше использовать) семантику планировщика Go - скорее наоборот. Могут быть некоторые кодовые трюки, которые могут привести к небольшому повышению производительности сегодня, но планировщик может и будет меняться в любом будущем релизе Go, делая ваши оптимизации кода бесполезными или даже работающими против вас.

Ответ 2

Два механизма могут усилить ваш контроль над этим:

  • runtime.Gosched() - возвращает управление планировщику, но, конечно, это не поможет с заблокированным вызовом, который вы уже выпустили.

  • runtime.LockOSThread() выделяет реальный поток ОС для этого goroutine и никого другого, что означает, что в планировщике будет меньше конкуренции. из документов:

LockOSThread wires the calling goroutine to its current operating system thread. Until the calling goroutine exits or calls UnlockOSThread, it will always execute in that thread, and no other goroutine can.

Ответ 3

Через Go 1.1, goroutines будет получать контроль над блокировкой вызовов (syscalls, чтение/запись каналов, блокировка мьютексов и т.д.). Это означало, что goroutines теоретически может полностью запугать CPU и не позволить планировщику работать.

В Go 1.2 было внесено изменение, чтобы исправить это:

В предыдущих выпусках goroutine, который зацикливался навсегда, мог бы изгонять других goroutines в одном и том же потоке, что является серьезной проблемой, когда GOMAXPROCS предоставил только один пользовательский поток. В Go 1.2 это частично исправлено: планировщик иногда вызывается при входе в функцию. Это означает, что любой цикл, который включает вызов функции (не вложенной), может быть предварительно упущен, что позволяет другим гортанам работать в одном потоке.

(источник: примечания к выпуску go1.2)

В теории все еще возможно, что goroutines запускают CPU, никогда не вызывая не-встроенную функцию, но такие случаи намного реже, чем goroutines, которые никогда не делают блокирующих вызовов, что и до вас до Go 1.2.