Горутины и потоки
От: mrTwister Россия  
Дата: 28.06.21 05:37
Оценка:
А почему, собственно, потоки операционной системы не могут быть столь же эффективны, как и горутины? То есть почему разработчики OS тоже не могут сделать динамический стек и другие оптимизации?
лэт ми спик фром май харт
Re: Горутины и потоки
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 28.06.21 07:45
Оценка: 4 (2)
Здравствуйте, mrTwister, Вы писали:

T>А почему, собственно, потоки операционной системы не могут быть столь же эффективны, как и горутины? То есть почему разработчики OS тоже не могут сделать динамический стек и другие оптимизации?


1) Переключение контекста не бесплатное
2) Каждый поток кушает 1МБ под стек минимум
В основном потому что ОС не знает чем будет заниматься поток и делает многое "по умолчанию".

В Винде уже давно есть возможность создавать пулы потоков, очереди задач, ожидания IO и таймеров, что позволяет иметь много потоков в программе при минимуме потоков в ОС.
Re[2]: Горутины и потоки
От: mrTwister Россия  
Дата: 28.06.21 10:54
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>1) Переключение контекста не бесплатное

А в GO бесплатное? Почему OS не может делать как в GO Runtime?

G>2) Каждый поток кушает 1МБ под стек минимум

Почему OS не может делать динамический размер стека, как GO Runtime?

G>В основном потому что ОС не знает чем будет заниматься поток и делает многое "по умолчанию".

А каких именно знаний о потоке не хватает OS?
лэт ми спик фром май харт
Re[3]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 28.06.21 11:37
Оценка: 3 (1)
Здравствуйте, mrTwister, Вы писали:

T>Здравствуйте, gandjustas, Вы писали:


G>>1) Переключение контекста не бесплатное

T>А в GO бесплатное? Почему OS не может делать как в GO Runtime?
https://ru.bmstu.wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%BA%D1%81%D1%82%D0%B0
G>>2) Каждый поток кушает 1МБ под стек минимум
T>Почему OS не может делать динамический размер стека, как GO Runtime?
http://vsokovikov.narod.ru/New_MSDN_API/Process_thread/size_stack_thread.htm
G>>В основном потому что ОС не знает чем будет заниматься поток и делает многое "по умолчанию".
T>А каких именно знаний о потоке не хватает OS?

Длительность, размер стека. Кроме того есть TLS https://docs.microsoft.com/ru-RU/cpp/parallel/thread-local-storage-tls?view=msvc-160&viewFallbackFrom=vs-2017
и солнце б утром не вставало, когда бы не было меня
Re[3]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.06.21 12:44
Оценка: 58 (2)
Здравствуйте, mrTwister, Вы писали:

G>>1) Переключение контекста не бесплатное

T>А в GO бесплатное? Почему OS не может делать как в GO Runtime?

Большинство переключений в рантайме не выходят за рамки юзерланда.
При переключении через ОС неизбежно прохождение через ядро. В системах, где системный вызов дорог, это даёт существенные затраты.

Кроме того, во многих ОС ради изоляции процессов сделано много защит (особенно это важно в плане последних Meltdown/Spectre — при переключении могут сбрасывать целиком кэши) и не сделано оптимизации этого в случае переключения между нитями одного процесса. В Windows это ещё сложнее сделать потому, что в отличие от всех Unix систем права доступа могут быть разными для разных нитей, поэтому количество действий при переключении существенно увеличивается.

G>>2) Каждый поток кушает 1МБ под стек минимум

T>Почему OS не может делать динамический размер стека, как GO Runtime?

Вообще-то давно есть подходы типа split stack. Но они дорогие за счёт проверки необходимости такого сплита на каждом вызове функции.

В случае управляемых рантаймов, как в Go, Erlang и тому подобных, рантайм имеет возможность в случае разбухания стека переаллоцировать его целиком, исправив адреса возврата. ОС не имеет данных для этого и в результате не имеет права двигать стек.

G>>В основном потому что ОС не знает чем будет заниматься поток и делает многое "по умолчанию".

T>А каких именно знаний о потоке не хватает OS?

Например, по каждому байту в стеке, что он означает — данное или адрес, а если адрес, то что за этим адресом и как им можно управлять (можно ли двигать, двигает ли кто-то из самого процесса...)
The God is real, unless declared integer.
Re[4]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.06.21 12:49
Оценка: 7 (2)
Здравствуйте, Serginio1, Вы писали:

G>>>1) Переключение контекста не бесплатное

T>>А в GO бесплатное? Почему OS не может делать как в GO Runtime?
S>https://ru.bmstu.wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%BA%D1%81%D1%82%D0%B0

Это ссылка ни о чём. Всё то кэпство, что там написано, или идентично для всех переключений включая чисто userland, или может быть устранено (как минимум в Unix) при переключении между нитями одного процесса. Что может пойти не так при переключении на другую нить — составляет малую часть этого набора (если это не случай "имперсонации" в другой процесс в Windows).

S> Длительность, размер стека. Кроме того есть TLS https://docs.microsoft.com/ru-RU/cpp/parallel/thread-local-storage-tls?view=msvc-160&viewFallbackFrom=vs-2017


TLS аж ничем не мешает. При переключении на другую нить меняется, в современных реализациях, регистр FS или GS. По адресу в этом регистре находится персональная область конкретной нити.
The God is real, unless declared integer.
Re[3]: Горутины и потоки
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 28.06.21 13:00
Оценка:
Здравствуйте, mrTwister, Вы писали:

T>Здравствуйте, gandjustas, Вы писали:


G>>1) Переключение контекста не бесплатное

T>А в GO бесплатное? Почему OS не может делать как в GO Runtime?
По сравнению с ОС — бесплатное.
К сожалению не знаю как работет ГО рантайм.


G>>2) Каждый поток кушает 1МБ под стек минимум

T>Почему OS не может делать динамический размер стека, как GO Runtime?
В ОС тоже динамический, начинается с 1 МБ. Это число видимо результат большого количеста исследований.

G>>В основном потому что ОС не знает чем будет заниматься поток и делает многое "по умолчанию".

T>А каких именно знаний о потоке не хватает OS?
ОС не знает когда поток остановится и остановится ли вообще. ОС прерывает выполнение по кванту времени, поэтому нужно сохвранить все текуще состояние (стек). В "упавлемых" средах "поток" выполнения останавливается не тогда когда ОС решит, а тогда когда код дойдет до точки прерывания. В этой точке рантайм знает о текущем состоянии "потока", которое обычно в разы меньше чем весь стек.
Re[2]: Горутины и потоки
От: vsb Казахстан  
Дата: 28.06.21 13:26
Оценка: 6 (1)
Здравствуйте, gandjustas, Вы писали:

G>2) Каждый поток кушает 1МБ под стек минимум


Это неправда. Память выделяется по мере использования. Если поток не использует стек, то ему больше одной страницы и не будет выделяться.
Re[4]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.06.21 13:28
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>>>В основном потому что ОС не знает чем будет заниматься поток и делает многое "по умолчанию".

T>>А каких именно знаний о потоке не хватает OS?
G>ОС не знает когда поток остановится и остановится ли вообще.

Ну кроме случаев, когда нить сама делает вызов, который блокирует её выполнение )
а таких действий в сетевой нагрузке и GUI, например, большинство.

G> ОС прерывает выполнение по кванту времени, поэтому нужно сохвранить все текуще состояние (стек).


При любом прерывании выполнения надо сохранить текущее состояние.

G> В "упавлемых" средах "поток" выполнения останавливается не тогда когда ОС решит, а тогда когда код дойдет до точки прерывания.


И как определяется точка прерывания?
В Go это точно так же как в случае ОС — переход в ожидание, или просто вызов чего-то в рантайме. Пустой вечный цикл в Go заблокирует целиком одну системную нить рантайма.
В Erlang не так — там рантайм считает "редукции", но за это платится тратой процессора.

G> В этой точке рантайм знает о текущем состоянии "потока", которое обычно в разы меньше чем весь стек.


Оно ровно равно тут всему стеку плюс регистры (а не пространству, зарезервированному под стек).
The God is real, unless declared integer.
Re[5]: Горутины и потоки
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 28.06.21 13:35
Оценка:
Здравствуйте, netch80, Вы писали:

G>>ОС не знает когда поток остановится и остановится ли вообще.

N>Ну кроме случаев, когда нить сама делает вызов, который блокирует её выполнение )
N>а таких действий в сетевой нагрузке и GUI, например, большинство.
А конкретнее?

Если что планировщики ОС всгеда нормально справлялись с десктопной нагрузкой. необходимость обращатся с огромным количеством потоков есть только в сервреах.


G>> ОС прерывает выполнение по кванту времени, поэтому нужно сохвранить все текуще состояние (стек).

N>При любом прерывании выполнения надо сохранить текущее состояние.
Только "контролируемое" прерывание потребует гораздо меньше места для сохраннеия, чем прерывание в произвольном месте.


G>> В "упавлемых" средах "поток" выполнения останавливается не тогда когда ОС решит, а тогда когда код дойдет до точки прерывания.

N>И как определяется точка прерывания?
N>В Go это точно так же как в случае ОС — переход в ожидание, или просто вызов чего-то в рантайме. Пустой вечный цикл в Go заблокирует целиком одну системную нить рантайма.
Это фактически означает что планирощик Го не может использоваться в ОС для всех потоков.

N>Оно ровно равно тут всему стеку плюс регистры (а не пространству, зарезервированному под стек).

В C# можно в явном виде увидеть состояние "продожения" в async\await, оно сильно меньше чем весь стек + регистры.
Re[5]: Горутины и потоки
От: mrTwister Россия  
Дата: 28.06.21 13:50
Оценка:
Здравствуйте, netch80, Вы писали:

N>В Go это точно так же как в случае ОС — переход в ожидание, или просто вызов чего-то в рантайме. Пустой вечный цикл в Go заблокирует целиком одну системную нить рантайма.


Не заблокирует, начиная с версии 1.14 в Go реализована вытесняющая многозадачность, рантайм переключает горутины в произвольные моменты времени независимо от того, что они делают
лэт ми спик фром май харт
Re[4]: Горутины и потоки
От: mrTwister Россия  
Дата: 28.06.21 13:51
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>ОС не знает когда поток остановится и остановится ли вообще. ОС прерывает выполнение по кванту времени, поэтому нужно сохвранить все текуще состояние (стек). В "упавлемых" средах "поток" выполнения останавливается не тогда когда ОС решит, а тогда когда код дойдет до точки прерывания. В этой точке рантайм знает о текущем состоянии "потока", которое обычно в разы меньше чем весь стек.


В Go все тоже самое начиная с версии 1.14, там нет больше никаких точек прерывания, рантайм переключает горутины, когда ему захочется
лэт ми спик фром май харт
Re[6]: Горутины и потоки
От: mrTwister Россия  
Дата: 28.06.21 13:55
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>В C# можно в явном виде увидеть состояние "продожения" в async\await, оно сильно меньше чем весь стек + регистры.


А зачем весь стек сохранять, почему регистров недостаточно?
лэт ми спик фром май харт
Re[7]: Горутины и потоки
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 28.06.21 14:02
Оценка:
Здравствуйте, mrTwister, Вы писали:

T>Здравствуйте, gandjustas, Вы писали:


G>>В C# можно в явном виде увидеть состояние "продожения" в async\await, оно сильно меньше чем весь стек + регистры.


T>А зачем весь стек сохранять, почему регистров недостаточно?


Работает поток: вызвал функцию А, она функцию Б, она функцию В. Произошло прерывание. Через пару десятков мсек возвращает правление потоку 1, возвращает стек на место, возвращает регистры и thread local значения. И функция В как ни в чем не бывало продолжает выполняться, возвращает управление функции Б, а та в свою очрередь функции А.

В async\await все переменные, используемые после прерывания, попадают в "состояние". Стек вызовов не хранится, а хранится только номер точки прерывания в каждой функции в цепочки вызовов и в switch по этому номеру функция продожает выполняться с точки прерывания.
Re[6]: Горутины и потоки
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 28.06.21 14:03
Оценка:
Здравствуйте, mrTwister, Вы писали:

T>Здравствуйте, netch80, Вы писали:


N>>В Go это точно так же как в случае ОС — переход в ожидание, или просто вызов чего-то в рантайме. Пустой вечный цикл в Go заблокирует целиком одну системную нить рантайма.


T>Не заблокирует, начиная с версии 1.14 в Go реализована вытесняющая многозадачность, рантайм переключает горутины в произвольные моменты времени независимо от того, что они делают


А зачем? ОС справляется хуже? Или сам по коду точки прерывания расставляет?
Re[4]: Горутины и потоки
От: mrTwister Россия  
Дата: 28.06.21 14:04
Оценка:
Здравствуйте, netch80, Вы писали:

Ок, спасибо, стало яснее. Но тогда разработчики ОС могли бы, например, предоставить возможность запускать легковесные потоки отдельно от старых тяжеловесных, введя ограничение на безопасность таких потоков и необходимость предоставлять информацию по разметке стека.
лэт ми спик фром май харт
Re[8]: Горутины и потоки
От: mrTwister Россия  
Дата: 28.06.21 14:09
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>Работает поток: вызвал функцию А, она функцию Б, она функцию В. Произошло прерывание. Через пару десятков мсек возвращает правление потоку 1, возвращает стек на место, возвращает регистры и thread local значения. И функция В как ни в чем не бывало продолжает выполняться, возвращает управление функции Б, а та в свою очрередь функции А.


Стек сам вернется после возвращения стековых регистров, память процесса же не стирается.
лэт ми спик фром май харт
Re[7]: Горутины и потоки
От: mrTwister Россия  
Дата: 28.06.21 14:12
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>А зачем? ОС справляется хуже? Или сам по коду точки прерывания расставляет?


Ну вот и я задумался, почему рантайм может, а ОС нет. Никаких точек прерывания по коду не расставляется.
лэт ми спик фром май харт
Re[5]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 28.06.21 14:28
Оценка:
Здравствуйте, netch80, Вы писали:

N>Здравствуйте, Serginio1, Вы писали:


G>>>>1) Переключение контекста не бесплатное

T>>>А в GO бесплатное? Почему OS не может делать как в GO Runtime?
S>>https://ru.bmstu.wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%BA%D1%81%D1%82%D0%B0

N>Это ссылка ни о чём. Всё то кэпство, что там написано, или идентично для всех переключений включая чисто userland, или может быть устранено (как минимум в Unix) при переключении между нитями одного процесса. Что может пойти не так при переключении на другую нить — составляет малую часть этого набора (если это не случай "имперсонации" в другой процесс в Windows).

Ну там написано и про кэши при межпроцессном переключениию
https://ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%BA%D1%81%D1%82%D0%B0

Содержимое кэша (особенно это касается кэша первого уровня), накопленное и «оптимизированное» под выполнение одного потока, оказывается совершенно неприменимым к новому потоку, на который происходит переключение.
При переключении контекста на процесс, который до этого долгое время не использовался (см. Подкачка страниц), многие страницы могут физически отсутствовать в оперативной памяти, что порождает подгрузку вытесненных страниц из вторичной памяти.


С точки зрения прикладного уровня переключение контекста можно разделить на добровольное (voluntary) и принудительное (non-voluntary): выполняющийся процесс/поток может сам передать управление другому потоку либо ядро может насильно отобрать у него управление.

Ядро ОС может отобрать управление у выполняющегося процесса/потока при истечении кванта времени, выделенного на выполнение. С точки зрения программиста это означает, что управление могло уйти от потока в «самый неподходящий» момент времени, когда структуры данных могут находиться в противоречивом состоянии из-за того, что их изменение не было завершено.
Выполнение блокирующего системного вызова. Когда приложение производит ввод-вывод, ядро может решить, что можно отдать управление другому потоку/процессу в ожидании, пока запрошенный данным потоком дисковый либо сетевой ввод-вывод будет выполнен. Данный вариант является самым производительным.
Синхронизирующие примитивы ядра. Мьютексы, Семафоры и т. д. Это и есть основной источник проблем с производительностью. Недостаточно продуманная работа с синхронизирующими примитивами может приводить к десяткам тысяч, а в особо запущенных случаях — и к сотням тысяч переключений контекста в секунду. [источник не указан 2456 дней]
Системный вызов, явно ожидающий наступления события (select, poll, epoll, pause, wait,…) либо момента времени (sleep, nanosleep,..). Данный вариант является относительно производительным, так как ядро ОС имеет информацию об ожидающих процессах.


Поэтому async await то есть задачи предпочтительнее потоков
и солнце б утром не вставало, когда бы не было меня
Re[9]: Горутины и потоки
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 28.06.21 14:37
Оценка:
Здравствуйте, mrTwister, Вы писали:

T>Здравствуйте, gandjustas, Вы писали:


G>>Работает поток: вызвал функцию А, она функцию Б, она функцию В. Произошло прерывание. Через пару десятков мсек возвращает правление потоку 1, возвращает стек на место, возвращает регистры и thread local значения. И функция В как ни в чем не бывало продолжает выполняться, возвращает управление функции Б, а та в свою очрередь функции А.


T>Стек сам вернется после возвращения стековых регистров, память процесса же не стирается


То есть стеки всех потоков хранятся в одном адресном пространстве?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.