Горутины и потоки
От: 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>Стек сам вернется после возвращения стековых регистров, память процесса же не стирается


То есть стеки всех потоков хранятся в одном адресном пространстве?
Re[10]: Горутины и потоки
От: mrTwister Россия  
Дата: 28.06.21 14:43
Оценка: +1
Здравствуйте, gandjustas, Вы писали:

G>То есть стеки всех потоков хранятся в одном адресном пространстве?


В рамках одного процесса — да, стеки всех потоков этого процесса живут в одном адресном пространстве. Переключение контекста процесса — это уже другая история, там виртуальная память переключается (вместе со всеми данными, включая стеки).
лэт ми спик фром май харт
Re[5]: Горутины и потоки
От: Cyberax Марс  
Дата: 28.06.21 17:11
Оценка:
Здравствуйте, netch80, Вы писали:

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

Уже нет. В Go 1.14 добавили асинхронные прерывания: https://medium.com/a-journey-with-go/go-asynchronous-preemption-b5194227371c
Sapienti sat!
Re[7]: Горутины и потоки
От: Cyberax Марс  
Дата: 28.06.21 17:12
Оценка:
Здравствуйте, gandjustas, Вы писали:

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

G>А зачем? ОС справляется хуже? Или сам по коду точки прерывания расставляет?
Для отзывчивости GC. В первой фазе ему нужно на короткое время остановить все потоки на safepoint'ах.
Sapienti sat!
Re[7]: Горутины и потоки
От: Sharov Россия  
Дата: 28.06.21 17:20
Оценка:
Здравствуйте, gandjustas, Вы писали:

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

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

А зачем ОС для этого вообще?
Кодом людям нужно помогать!
Re[5]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.06.21 18:40
Оценка: 6 (1)
Здравствуйте, mrTwister, Вы писали:

T>Ок, спасибо, стало яснее. Но тогда разработчики ОС могли бы, например, предоставить возможность запускать легковесные потоки отдельно от старых тяжеловесных, введя ограничение на безопасность таких потоков и необходимость предоставлять информацию по разметке стека.


В какой-то мере это было, гуглить про "M:N threading". В Solaris такое делали, во FreeBSD пытались. Основные попытки шли в период 1995-2005 и оказалось, что это хуже работает, чем прямой 1:1, потому что сложность такого двухуровневого шедулинга забивает всё.

Сейчас, возможно, наступило время следующих попыток, на более высоком уровне.
The God is real, unless declared integer.
Re[6]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.06.21 18:46
Оценка:
Здравствуйте, Serginio1, Вы писали:

S> Ну там написано и про кэши при межпроцессном переключениию

S>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
S>

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


Да.

S>[q]

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

S>Ядро ОС может отобрать управление у выполняющегося процесса/потока при истечении кванта времени, выделенного на выполнение. С точки зрения программиста это означает, что управление могло уйти от потока в «самый неподходящий» момент времени, когда структуры данных могут находиться в противоречивом состоянии из-за того, что их изменение не было завершено.


И что? ОС всё равно должна будет сделать это рано или поздно, если задача сама не отдаёт управление. А если отдаёт, то зачем ОС принудительно переключать?

S> Поэтому async await то есть задачи предпочтительнее потоков


Нет, само по себе ничего из цитированного не является тут аргументом в сторону async/await. Там точно так же — если управление отдано явно, то шедулер не будет забирать насильно, а если нет, то заберёт — скорее всего, средствами ОС. Более того, если в момент переключения на входе или выходе await не будет явно сказано "а теперь подумайте, не переключить ли" соответствующим системным вызовом, то ОС не будет знать, когда там userland занимается переключением вокруг awaitʼа, и тоже переключит в непредсказуемый (и, возможно, неподходящий) момент.
The God is real, unless declared integer.
Re[10]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.06.21 18:50
Оценка: 6 (1)
Здравствуйте, gandjustas, Вы писали:

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


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


G>То есть стеки всех потоков хранятся в одном адресном пространстве?


Естественно, в одном. Все нитки видят все стеки и могут обращаться в пределах разрешённого системой защиты.
А иначе бы не работало, например, когда в main() создаётся объект глобальной обстановки и он дальше по ссылке/указателю передаётся остальным, или когда рабочие нитки пула обращаются к состоянию управляющей нитки.

И даже thread-local память сидит в общем пространстве — меняется только приписанный к конкретной нитке адрес начала персональной области (на x86 обычно в регистре FS и/или GS).
The God is real, unless declared integer.
Re[6]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.06.21 18:58
Оценка: 6 (1)
Здравствуйте, gandjustas, Вы писали:

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

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

Эээ... что именно конкретнее?

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


А что из этого влияет на сказанное мной?
И IO bound, и CPU bound нагрузка может быть и на сервере, и на десктопе.

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

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

Ну да, потому что необходимое для этого сохранение предварительно сделано (а потом будет восстановление) самим прерываемым кодом. Сохранения в идеальном варианте столько же, но отличается место и исполнитель. Если же кто-то видит суммарный объём действий по такому сохранению меньше, чем в варианте разрыва в произвольном месте, то там неэффективно используют ресурсы процессора — в первую очередь заполняют не все регистры.

(В частности, этим как раз отличается Go — там из-за NIH написания кодогенератора нет передачи аргументов и результатов функции в регистрах, всё на стеке, и даже внутри функций основная часть кода это прочитать из одного места стека и записать тут же в другое, а потом обратно.)

Там, где ресурсы используются эффективно, неизбежно при переключениях сохраняются и восстанавливаются все неслужебные регистры, и неважно, переключение произошло добровольно или через таймерный сигнал.

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

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

Про то, что недавно это таки исправили, уже написали.
Но в общем да, внутренний планировщик Go не может использоваться в ядре, или о чём речь?

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

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

В таком случае сохранение и восстановление сделано внутренними средствами рантайма как раз на границе точки разрезания. Как это конкретно сделано — значения скинуты в стек, в состояние объекта или ещё куда-то — это уже немного менее важно.
The God is real, unless declared integer.
Отредактировано 29.06.2021 6:21 netch80 . Предыдущая версия .
Re[5]: Горутины и потоки
От: Sharov Россия  
Дата: 29.06.21 00:04
Оценка:
Здравствуйте, netch80, Вы писали:


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


А чем системная нить rt в Го отличается от потока ОС?


N>В Erlang не так — там рантайм считает "редукции", но за это платится тратой процессора.


А это что такое, в двух словах, если можно?
Кодом людям нужно помогать!
Re[7]: Горутины и потоки
От: Sharov Россия  
Дата: 29.06.21 00:39
Оценка:
Здравствуйте, netch80, Вы писали:

N>Про то, что недавно это таки исправили, уже написали.

N>Но в общем да, внутренний планировщик Go не может использоваться в ядре, или о чём речь?

Ну так это и есть суть вопроса ТС -- потоки переключает соотв. планировщик (го, .net и т.п.), а уже ОС только процессы.
В таком случае зеленые потоки будут просто не нужны. Ну т.е. в ядре ОС выполнять код соотв. стороннего планировщика.
Кодом людям нужно помогать!
Re[8]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 29.06.21 05:56
Оценка: 93 (3)
Здравствуйте, Sharov, Вы писали:

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


N>>Про то, что недавно это таки исправили, уже написали.

N>>Но в общем да, внутренний планировщик Go не может использоваться в ядре, или о чём речь?

S>Ну так это и есть суть вопроса ТС


Как по мне, вопрос заметно в другом.

S> -- потоки переключает соотв. планировщик (го, .net и т.п.), а уже ОС только процессы.


В таком виде на современном железе никому не будет нужно.
Больше одного харта (hardware thread) => можно исполнять несколько задач (любого вида) одновременно, значит, надо уметь исполнять и несколько нитей (тредов), которые с точки зрения ОС относятся к одному процессу. Что в процессе это будут именно диспетчерские нити, которые будут выполнять по своему желанию конкретные нити целевого кода, это уже не вопрос ОС.

S>В таком случае зеленые потоки будут просто не нужны. Ну т.е. в ядре ОС выполнять код соотв. стороннего планировщика.


И кто пустит посторонний код в ядро?

Похожие разработки как раз были. Во FreeBSD игрались с подходом "scheduler activations". Это выглядело так: если нить блокируется в ядре, ядро запускает указанный процессом пользовательский код с указанием "а подумай, не переключиться ли на что-то ещё". Пользовательский код управляет тем, какие из видимых ниток должны исполняться.
Разработка шла приблизительно в 1999-2002. После отказались и сделали тупо 1:1, все нити процесса известны ядру и оно их переключает. Вот не справились со сложностью.
Насколько мне известно, после этого таких попыток не было.
Go, Erlang, прочие — там внутренний шедулер процесса имеет сильно больше возможностей знать контекст и управлять им, и поэтому ему легче.
The God is real, unless declared integer.
Re[6]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 29.06.21 05:59
Оценка: 6 (1)
Здравствуйте, Sharov, Вы писали:

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

S>А чем системная нить rt в Го отличается от потока ОС?

Это одно и то же. (И, на всякий случай, нить = thread в нормальной терминологии, а поток это stream или flow, но не thread.)
Рантайм Go держит максимум указанное в GOMAXPROCS (да, название сомнительно) количество рабочих нитей (умолчание равно количеству harts == hardware threads, которое равно количеству ядер без гипертрединга и умножается на количество тредов в ядре для включённого гипертрединга; в документации Go, однако, эти сущности называются CPU). Внутренний планировщик решает, какую горутину будет исполнять конкретная нить.

N>>В Erlang не так — там рантайм считает "редукции", но за это платится тратой процессора.

S>А это что такое, в двух словах, если можно?

Выполнение одного элементарного действия (вызов функции, выполнение арифметической операции...)
The God is real, unless declared integer.
Отредактировано 29.06.2021 6:11 netch80 . Предыдущая версия .
Re[7]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 29.06.21 15:11
Оценка:
Здравствуйте, netch80, Вы писали:


S>>Ядро ОС может отобрать управление у выполняющегося процесса/потока при истечении кванта времени, выделенного на выполнение. С точки зрения программиста это означает, что управление могло уйти от потока в «самый неподходящий» момент времени, когда структуры данных могут находиться в противоречивом состоянии из-за того, что их изменение не было завершено.


N>И что? ОС всё равно должна будет сделать это рано или поздно, если задача сама не отдаёт управление. А если отдаёт, то зачем ОС принудительно переключать?

То что желательно указать в какие моменты можно переключать. Например дождаться выполнения lock
S>> Поэтому async await то есть задачи предпочтительнее потоков

N>Нет, само по себе ничего из цитированного не является тут аргументом в сторону async/await. Там точно так же — если управление отдано явно, то шедулер не будет забирать насильно, а если нет, то заберёт — скорее всего, средствами ОС. Более того, если в момент переключения на входе или выходе await не будет явно сказано "а теперь подумайте, не переключить ли" соответствующим системным вызовом, то ОС не будет знать, когда там userland занимается переключением вокруг awaitʼа, и тоже переключит в непредсказуемый (и, возможно, неподходящий) момент.

Ну как же не. Как раз проблема в большей степени это проблемы с Синхронизирующие примитивы ядра. Мьютексы, Семафоры, эвенты и т. д.

Для примера в эпоху до async/await поток ждет выполнения асинхронной операции.
async/await берет на себя сохранения данных внутри класса (стек не нужен) и строит автомат и тот же поток который выполнял данную задачу, запускает другую. Нет никаких переключений.
Не зря же используют SpinLock. Если бы стоимость переключения была не важна, то нафига он нужен?
Зачем тогда async/await

Ну и замена всяких Lock на ManualResetValueTaskSource
http://rsdn.org/forum/dotnet/8030645.1
Автор: Serginio1
Дата: 16.06.21

https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask

Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.
и солнце б утром не вставало, когда бы не было меня
Re[2]: Горутины и потоки
От: vdimas Россия  
Дата: 29.06.21 23:42
Оценка:
Здравствуйте, gandjustas, Вы писали:

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


Для потоков одного процесса должно быть бесплатным.

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


Настраивается.
И эта же проблема присутствует в любой реализации "зеленых потоков".
Re[3]: Горутины и потоки
От: mrTwister Россия  
Дата: 30.06.21 07:08
Оценка:
Здравствуйте, vdimas, Вы писали:

V>И эта же проблема присутствует в любой реализации "зеленых потоков".


В горутинах этой проблемы нет, там размер стека динамический: начинает с 2Kb, и может увеличиваться по мере надобности до 1Gb
лэт ми спик фром май харт
Re[4]: Горутины и потоки
От: vdimas Россия  
Дата: 30.06.21 10:13
Оценка:
Здравствуйте, mrTwister, Вы писали:

V>>И эта же проблема присутствует в любой реализации "зеленых потоков".

T>В горутинах этой проблемы нет, там размер стека динамический: начинает с 2Kb, и может увеличиваться по мере надобности до 1Gb

Это можно сделать и в С++ с некоторыми ограничениями, а именно — не вываливать наружу адреса стековых объектов и чтобы сами объекты, создаваемые по-значению на стеке, не оперировали адресами своего расположения. В управляемых языках, поддерживающих перемещение объектов в памяти как часть алгоритма GC, ограничений меньше, ес-но.

Прологи-эпилоги для каждой архитектуры известны, переполнение стека перехватывается, можно выделять новые фреймы стека по мере надобности, перенося туда стековую память текущей ф-ии, которой не хватило стека.
Re[11]: Горутины и потоки
От: Sharov Россия  
Дата: 30.06.21 10:51
Оценка:
Здравствуйте, netch80, Вы писали:

N>И даже thread-local память сидит в общем пространстве — меняется только приписанный к конкретной нитке адрес начала персональной области (на x86 обычно в регистре FS и/или GS).


А как локальность страницы в FS\GS решается? На уровне параметров (дескриптора) самой страницы? Ну т.е. почему DS общий для всего
процесса (всех потоков), а fs только для одного?
Кодом людям нужно помогать!
Re[12]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 30.06.21 16:01
Оценка: 6 (1)
Здравствуйте, Sharov, Вы писали:

N>>И даже thread-local память сидит в общем пространстве — меняется только приписанный к конкретной нитке адрес начала персональной области (на x86 обычно в регистре FS и/или GS).


S>А как локальность страницы в FS\GS решается? На уровне параметров (дескриптора) самой страницы?


Локальность в смысле доступа только одной нити — никак не решается — каждая может что-то сделать с данными каждой другой нити, если знает, как (знает адрес).
Все TLS области мапятся на одно общее адресное пространство процесса.

S> Ну т.е. почему DS общий для всего

S>процесса (всех потоков), а fs только для одного?

FS и GS в этом режиме это уже не сегментные дескрипторы, а просто регистры — указатели (причём в x86-32 это опционально, а в x86-64 уже форсировано).
Просто служебный регистр, который можно использовать только как базовый адрес чего-то. Вот он и используется как адрес thread-local области.

Ну и DS в обычном сетапе в x86-32, и единственно возможно в x86-64, это просто "вся виртуальная память с адреса 0".
The God is real, unless declared integer.
Re[8]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 30.06.21 18:23
Оценка:
Здравствуйте, Serginio1, Вы писали:

N>>И что? ОС всё равно должна будет сделать это рано или поздно, если задача сама не отдаёт управление. А если отдаёт, то зачем ОС принудительно переключать?

S>То что желательно указать в какие моменты можно переключать. Например дождаться выполнения lock

Эээ... после выполнения захвата лока (речь об этом, да?) как раз лучше не отдавать управление, а сделать максимум действий во время захвата. А до него — если лок занят — то раз ждём, надо оповестить шедулер о том, что есть отличный повод запустить другую задачу.

N>>Нет, само по себе ничего из цитированного не является тут аргументом в сторону async/await. Там точно так же — если управление отдано явно, то шедулер не будет забирать насильно, а если нет, то заберёт — скорее всего, средствами ОС. Более того, если в момент переключения на входе или выходе await не будет явно сказано "а теперь подумайте, не переключить ли" соответствующим системным вызовом, то ОС не будет знать, когда там userland занимается переключением вокруг awaitʼа, и тоже переключит в непредсказуемый (и, возможно, неподходящий) момент.

S> Ну как же не. Как раз проблема в большей степени это проблемы с Синхронизирующие примитивы ядра. Мьютексы, Семафоры, эвенты и т. д.

И в чём тут проблема?

S>Для примера в эпоху до async/await поток ждет выполнения асинхронной операции.

S>async/await берет на себя сохранения данных внутри класса (стек не нужен) и строит автомат и тот же поток который выполнял данную задачу, запускает другую. Нет никаких переключений.

А что, вот эти все "берёт на себя сохранения данных внутри класса" и аналогичное восстановление, по-вашему, не переключение? А что оно такое тогда?

S> Не зря же используют SpinLock. Если бы стоимость переключения была не важна, то нафига он нужен?


Вот этот, да?

Действительно, стоимость переключения (любого — в том числе которого вы не замечаете цитатой выше) высока. Но при чём тут спинлоки?
Спинлок это очень специфичное средство, которое становится эффективным только при одновременном выполнении двух условий: 1) конкуренты за лок чаще находятся на разных физических исполнителях (железных тредах, harts) и 2) длительность возможной конкуренции относительно мала. Иначе трата процессора на кручение вокруг ячейки памяти в ожидании, пока в той появится заветный нолик, слишком велика — лучше таки переключиться.

Классический мьютекс ещё со времён Solaris поэтому делает N попыток захвата как спинлока (процессорной операцией типа CAS, LL/SC), а если не получилось — уходит в ядерный вызов. В Linux сделано (в futex) так же. Что в Windows, не знаю, облом докапываться до исходников. Но использовать SpinLock как аргумент в пользу дороговизны переключения — это, мягко говоря, странно.

S>Зачем тогда async/await


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

S>Ну и замена всяких Lock на ManualResetValueTaskSource

S>http://rsdn.org/forum/dotnet/8030645.1
Автор: Serginio1
Дата: 16.06.21

S> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask

Я что-то не могу это раскурить. Какой смысл в его применении?

S>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.


Тоже не понимаю, при чём тут эта реплика.
The God is real, unless declared integer.
Re[9]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 01.07.21 07:58
Оценка:
Здравствуйте, netch80, Вы писали:

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


N>>>И что? ОС всё равно должна будет сделать это рано или поздно, если задача сама не отдаёт управление. А если отдаёт, то зачем ОС принудительно переключать?

S>>То что желательно указать в какие моменты можно переключать. Например дождаться выполнения lock

N>Эээ... после выполнения захвата лока (речь об этом, да?) как раз лучше не отдавать управление, а сделать максимум действий во время захвата. А до него — если лок занят — то раз ждём, надо оповестить шедулер о том, что есть отличный повод запустить другую задачу.

Я как раз про то, что если lock короткий, то не стоит передавать управление на другой поток.

N>>>Нет, само по себе ничего из цитированного не является тут аргументом в сторону async/await. Там точно так же — если управление отдано явно, то шедулер не будет забирать насильно, а если нет, то заберёт — скорее всего, средствами ОС. Более того, если в момент переключения на входе или выходе await не будет явно сказано "а теперь подумайте, не переключить ли" соответствующим системным вызовом, то ОС не будет знать, когда там userland занимается переключением вокруг awaitʼа, и тоже переключит в непредсказуемый (и, возможно, неподходящий) момент.

S>> Ну как же не. Как раз проблема в большей степени это проблемы с Синхронизирующие примитивы ядра. Мьютексы, Семафоры, эвенты и т. д.

N>И в чём тут проблема?


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

Синхронизирующие примитивы ядра. Мьютексы, Семафоры и т. д. Это и есть основной источник проблем с производительностью. Недостаточно продуманная работа с синхронизирующими примитивами может приводить к десяткам тысяч, а в особо запущенных случаях — и к сотням тысяч переключений контекста в секунду. [источник не указан 2477 дней]

S>>Для примера в эпоху до async/await поток ждет выполнения асинхронной операции.
S>>async/await берет на себя сохранения данных внутри класса (стек не нужен) и строит автомат и тот же поток который выполнял данную задачу, запускает другую. Нет никаких переключений.

N>А что, вот эти все "берёт на себя сохранения данных внутри класса" и аналогичное восстановление, по-вашему, не переключение? А что оно такое тогда?

Там нет восстаеовления ибо данные хранятся в куче. Да должны закинуть this в регистр и вызвать MoveNext
S>> Не зря же используют SpinLock. Если бы стоимость переключения была не важна, то нафига он нужен?

N>Вот этот, да?


N>Действительно, стоимость переключения (любого — в том числе которого вы не замечаете цитатой выше) высока. Но при чём тут спинлоки?

N>Спинлок это очень специфичное средство, которое становится эффективным только при одновременном выполнении двух условий: 1) конкуренты за лок чаще находятся на разных физических исполнителях (железных тредах, harts) и 2) длительность возможной конкуренции относительно мала. Иначе трата процессора на кручение вокруг ячейки памяти в ожидании, пока в той появится заветный нолик, слишком велика — лучше таки переключиться.

N>Классический мьютекс ещё со времён Solaris поэтому делает N попыток захвата как спинлока (процессорной операцией типа CAS, LL/SC), а если не получилось — уходит в ядерный вызов. В Linux сделано (в futex) так же. Что в Windows, не знаю, облом докапываться до исходников. Но использовать SpinLock как аргумент в пользу дороговизны переключения — это, мягко говоря, странно.


S>>Зачем тогда async/await


N>С точки зрения логики управления выполнением — именно для тех случаев, когда шедулер узнал, что ответа для await сейчас нет и надо дать кому-то управление.

N>С точки зрения исходного кода — чтобы писать его максимально линейно.
Линейно то и раньше писали, только внутри были всякие евенты и остановка потока.
N>Переключений в нём, в идеале, столько же, сколько в аналогичном коде на коллбэках или промисах.
Ну в итоге то колбеки запускаются из пула потоков.

S>>Ну и замена всяких Lock на ManualResetValueTaskSource

S>>http://rsdn.org/forum/dotnet/8030645.1
Автор: Serginio1
Дата: 16.06.21

S>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask

N>Я что-то не могу это раскурить. Какой смысл в его применении?

Смысл в том, что замена lock на lockAsync. То есть нет никакого ожидания потока
S>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.

N>Тоже не понимаю, при чём тут эта реплика.

В том, что с использованием пула потоков сводит к минимуму переключение потоков
и солнце б утром не вставало, когда бы не было меня
Re[3]: Горутины и потоки
От: Cyberax Марс  
Дата: 01.07.21 08:23
Оценка:
Здравствуйте, vdimas, Вы писали:

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

V>Для потоков одного процесса должно быть бесплатным.
Переключение в режим ядра и обратно никогда не бывает бесплатным.

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

V>Настраивается.
С учётом служебных структур и прочего — в Линуксе около 32Кб. Но это экстремально мало.

V>И эта же проблема присутствует в любой реализации "зеленых потоков".

В Го стек занимает 2048 байт при создании, и потом по необходимости растёт.
Sapienti sat!
Re[9]: Горутины и потоки
От: Sharov Россия  
Дата: 01.07.21 11:36
Оценка:
Здравствуйте, netch80, Вы писали:


S>>Зачем тогда async/await

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

async\await и есть по сути promise.


S>>Ну и замена всяких Lock на ManualResetValueTaskSource

S>>http://rsdn.org/forum/dotnet/8030645.1
Автор: Serginio1
Дата: 16.06.21

S>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask
N>Я что-то не могу это раскурить. Какой смысл в его применении?

Вероятно речь о том, что раньше тип task был классом, то теперь может быть (или есть всегда) структура,
соотв. меньше напряг на gc. Например, когда запускаем задачу, которая выполняется синхронно, т.е.
в том же потоке и получается, что task создали зря, лишний напряг на gc. Какое отношение это имеет к дискуссии --
Кодом людям нужно помогать!
Re[10]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 01.07.21 15:31
Оценка:
Здравствуйте, Sharov, Вы писали:

S>>>Зачем тогда async/await

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

S>async\await и есть по сути promise.


Да. Но в случае await просто пишете x = await y(), а для промисов (я имею в виду стиля JS) надо конструировать что-то вроде

p = new Promise(function() { y(); }).then(function(result) { x = result; });


в общем, громоздко.

S>>>Ну и замена всяких Lock на ManualResetValueTaskSource

S>>>http://rsdn.org/forum/dotnet/8030645.1
Автор: Serginio1
Дата: 16.06.21

S>>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask
N>>Я что-то не могу это раскурить. Какой смысл в его применении?

S>Вероятно речь о том, что раньше тип task был классом, то теперь может быть (или есть всегда) структура,

S>соотв. меньше напряг на gc. Например, когда запускаем задачу, которая выполняется синхронно, т.е.
S> в том же потоке и получается, что task создали зря, лишний напряг на gc. Какое отношение это имеет к дискуссии --

Ну меньше напряг — всегда полезно но таки да, не в тему.
The God is real, unless declared integer.
Re[10]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 02.07.21 05:10
Оценка:
Здравствуйте, Serginio1, Вы писали:

N>>Эээ... после выполнения захвата лока (речь об этом, да?) как раз лучше не отдавать управление, а сделать максимум действий во время захвата. А до него — если лок занят — то раз ждём, надо оповестить шедулер о том, что есть отличный повод запустить другую задачу.

S> Я как раз про то, что если lock короткий, то не стоит передавать управление на другой поток.

Так передавать когда? До него или после? В случае удачного захвата или неудачного?

Не хочу искать, что в Windows. В Linux это выглядит (для внутрипроцессных мьютексов): читаем переменную, которая собственно представляет мьютекс для процесса. Могут быть варианты: 0, 1, 2. 2 значит, что мьютекс залочен и кто-то уже стоит в очереди на него, тогда сразу идём в ядерный вызов становиться в хвост. Если 0 или 1, делаем сколько-то (грубо говоря, 100) попыток захватить его через команду процессора (считай, cmpxchg), если получилось (было 0 и мы записали 1) — ok, работаем, иначе кончились попытки — снова становимся в очередь уже через ядерный вызов.
Освобождение похоже: читаем ячейку; 2 — значит, за нами стоят в очереди и ядерный вызов необходим, чтобы его разбудить. 1 — записываем туда 0 и знаем, что если кто-то захочет, он придёт без дополнительной синхронизации.
Так вот пока крутится цикл с cmpxchg — ядро не дёргаем и ему нет причины задуматься, переключать ли на кого-то ещё, процесс себе продолжает работать. Условие "не стоит передавать управление", насколько я вижу, выполнено идеально.

S>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

S>

S>Синхронизирующие примитивы ядра. Мьютексы, Семафоры и т. д. Это и есть основной источник проблем с производительностью. Недостаточно продуманная работа с синхронизирующими примитивами может приводить к десяткам тысяч, а в особо запущенных случаях — и к сотням тысяч переключений контекста в секунду. [источник не указан 2477 дней]


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

S>>>Для примера в эпоху до async/await поток ждет выполнения асинхронной операции.

S>>>async/await берет на себя сохранения данных внутри класса (стек не нужен) и строит автомат и тот же поток который выполнял данную задачу, запускает другую. Нет никаких переключений.

N>>А что, вот эти все "берёт на себя сохранения данных внутри класса" и аналогичное восстановление, по-вашему, не переключение? А что оно такое тогда?

S> Там нет восстаеовления ибо данные хранятся в куче.

Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора.

Не может быть квадратных кругов и круглых квадратов. Если с данными надо работать, то для этого нужен доступ к ним. Если нужен доступ, их надо прочитать из памяти и записать в память, с соответствующей ценой. Я бы предпочёл, чтобы компиляция хранила максимум в регистрах процессора, а что не помещается — максимум на стеке: не будет дурных потерь скорости. А если при этом само переключение формально дороже — тоже не страшно, записать компактно пару сотен байт и потом прочитать — эффективнее, чем размазывать их по десяткам кэш-строк.

N>>С точки зрения логики управления выполнением — именно для тех случаев, когда шедулер узнал, что ответа для await сейчас нет и надо дать кому-то управление.

N>>С точки зрения исходного кода — чтобы писать его максимально линейно.
S> Линейно то и раньше писали, только внутри были всякие евенты и остановка потока.

Я говорю про варианты без такой остановки (если переключение, то оно минимально видно в ОС).

N>>Переключений в нём, в идеале, столько же, сколько в аналогичном коде на коллбэках или промисах.

S> Ну в итоге то колбеки запускаются из пула потоков.

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

S>>>Ну и замена всяких Lock на ManualResetValueTaskSource

S>>>http://rsdn.org/forum/dotnet/8030645.1
Автор: Serginio1
Дата: 16.06.21

S>>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask

N>>Я что-то не могу это раскурить. Какой смысл в его применении?

S> Смысл в том, что замена lock на lockAsync. То есть нет никакого ожидания потока

Если оно сделано умно (с попытками захвата до переключения) — отлично.

S>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.

N>>Тоже не понимаю, при чём тут эта реплика.
S> В том, что с использованием пула потоков сводит к минимуму переключение потоков

Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен.
The God is real, unless declared integer.
Re[9]: Горутины и потоки
От: Cyberax Марс  
Дата: 02.07.21 06:12
Оценка:
Здравствуйте, netch80, Вы писали:

S>>В таком случае зеленые потоки будут просто не нужны. Ну т.е. в ядре ОС выполнять код соотв. стороннего планировщика.

N>И кто пустит посторонний код в ядро?
Можно eBPF использовать

N>Похожие разработки как раз были. Во FreeBSD игрались с подходом "scheduler activations". Это выглядело так: если нить блокируется в ядре, ядро запускает указанный процессом пользовательский код с указанием "а подумай, не переключиться ли на что-то ещё". Пользовательский код управляет тем, какие из видимых ниток должны исполняться.

N>Разработка шла приблизительно в 1999-2002. После отказались и сделали тупо 1:1, все нити процесса известны ядру и оно их переключает. Вот не справились со сложностью.
Проблема в том, что переключения контекстов уж слишком дорогие.
Sapienti sat!
Re[11]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 02.07.21 07:25
Оценка:
Здравствуйте, netch80, Вы писали:

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


N>>>Эээ... после выполнения захвата лока (речь об этом, да?) как раз лучше не отдавать управление, а сделать максимум действий во время захвата. А до него — если лок занят — то раз ждём, надо оповестить шедулер о том, что есть отличный повод запустить другую задачу.

S>> Я как раз про то, что если lock короткий, то не стоит передавать управление на другой поток.

N>Так передавать когда? До него или после? В случае удачного захвата или неудачного?

При удачном захвате стоит дождаться освобождения, что бы другие потоки могли воспользоваться свободным монитором


N>Да, проблема есть (хотя про "основной источник проблем с производительностью" тут загнули, случай он разный бывает). Но вы никак от этого не избавитесь ни шедулингом, ни переходом на await, пока вам потребуется ровно та же синхронизация.


Я тебе привел пример аналога LockAsync. Поток ничего не ждет, а по событию запускается из пула потоков. Вот пример асинхронной очереди
AsyncProducerConsumerCollection
Просто привел пример с использованием ValueTask вместо Task

S>>>>Для примера в эпоху до async/await поток ждет выполнения асинхронной операции.

S>>>>async/await берет на себя сохранения данных внутри класса (стек не нужен) и строит автомат и тот же поток который выполнял данную задачу, запускает другую. Нет никаких переключений.

N>>>А что, вот эти все "берёт на себя сохранения данных внутри класса" и аналогичное восстановление, по-вашему, не переключение? А что оно такое тогда?

S>> Там нет восстаеовления ибо данные хранятся в куче.

N>Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора.

Угу который при переходе на новый поток сбрасывается.
У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?

N>Не может быть квадратных кругов и круглых квадратов. Если с данными надо работать, то для этого нужен доступ к ним. Если нужен доступ, их надо прочитать из памяти и записать в память, с соответствующей ценой. Я бы предпочёл, чтобы компиляция хранила максимум в регистрах процессора, а что не помещается — максимум на стеке: не будет дурных потерь скорости. А если при этом само переключение формально дороже — тоже не страшно, записать компактно пару сотен байт и потом прочитать — эффективнее, чем размазывать их по десяткам кэш-строк.


Ну класс если используешь Value типы тоже будут компактно расположены, а не валуе типы что на стеке, что не на нем все равно в куче

N>>>С точки зрения логики управления выполнением — именно для тех случаев, когда шедулер узнал, что ответа для await сейчас нет и надо дать кому-то управление.

N>>>С точки зрения исходного кода — чтобы писать его максимально линейно.
S>> Линейно то и раньше писали, только внутри были всякие евенты и остановка потока.

N>Я говорю про варианты без такой остановки (если переключение, то оно минимально видно в ОС).


N>>>Переключений в нём, в идеале, столько же, сколько в аналогичном коде на коллбэках или промисах.

S>> Ну в итоге то колбеки запускаются из пула потоков.

N>Ну это уже дотнетовая специфика и не обязательно так везде. И это не обязательно удобно. Я во многих случаях хотел бы ограничить такое явно только тем же тредом, который вызвал исходную операцию.


S>>>>Ну и замена всяких Lock на ManualResetValueTaskSource

S>>>>http://rsdn.org/forum/dotnet/8030645.1
Автор: Serginio1
Дата: 16.06.21

S>>>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask

N>>>Я что-то не могу это раскурить. Какой смысл в его применении?

S>> Смысл в том, что замена lock на lockAsync. То есть нет никакого ожидания потока

N>Если оно сделано умно (с попытками захвата до переключения) — отлично.


S>>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.

N>>>Тоже не понимаю, при чём тут эта реплика.
S>> В том, что с использованием пула потоков сводит к минимуму переключение потоков

N>Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен.

Вот ссылочка из предыдущего сообщения AsyncSemaphore

Если lock занят то возвращается Task и ожидается пока не вызовется SetResult
Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock
и солнце б утром не вставало, когда бы не было меня
Re[12]: Горутины и потоки
От: Cyberax Марс  
Дата: 02.07.21 23:05
Оценка: 12 (1)
Здравствуйте, Serginio1, Вы писали:

S>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?

Очередь потоков? Пул потоков, в смысле?

Из-за того, что системные потоки очень дорого запускать с нуля.
Sapienti sat!
Re[12]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 03.07.21 07:29
Оценка:
Здравствуйте, Serginio1, Вы писали:

N>>>>Эээ... после выполнения захвата лока (речь об этом, да?) как раз лучше не отдавать управление, а сделать максимум действий во время захвата. А до него — если лок занят — то раз ждём, надо оповестить шедулер о том, что есть отличный повод запустить другую задачу.

S>>> Я как раз про то, что если lock короткий, то не стоит передавать управление на другой поток.

N>>Так передавать когда? До него или после? В случае удачного захвата или неудачного?

S>При удачном захвате стоит дождаться освобождения, что бы другие потоки могли воспользоваться свободным монитором

Вероятность прерывания в этот момент для интерактивной нитки (который больше спит и реагирует на события, чем ест процессор) крайне мала (при сколь-нибудь умном шедулере, разумеется).

Более того, в случае await-based построения работы вероятность насильственного переключения со стороны ОС выше, если нет возможности её явно проинструктировать "тут воздержись, а тут больше свободы" — если гружёный процесс держит всю активность в нескольких нитках по количеству хартов — с точки зрения ОС это одна неуправляемая прожора, и когда переключать на соседей — она просто не в курсе.

N>>Да, проблема есть (хотя про "основной источник проблем с производительностью" тут загнули, случай он разный бывает). Но вы никак от этого не избавитесь ни шедулингом, ни переходом на await, пока вам потребуется ровно та же синхронизация.

S> Я тебе привел пример аналога LockAsync. Поток ничего не ждет, а по событию запускается из пула потоков. Вот пример асинхронной очереди
S>AsyncProducerConsumerCollection
S>Просто привел пример с использованием ValueTask вместо Task

Так с точки зрения ОС что поможет тут осознать, когда можно переключать на другую нить вообще другого процесса, а когда лучше этого не делать, потому что захвачен важный лок (и даже в случае асинхронных локов это блокирует другие задачи)?
Я б ожидал каких-то явных системных вызовов, но тут про них ни слова.

N>>Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора.

S> Угу который при переходе на новый поток сбрасывается.

Кто сбрасывается? Кэш процессора? Зачем? Даже с мерами против Meltdown/Spectre кэш сбрасывается при переключении между процессами, а не между нитками одного процесса. Хотя о чём и, главное, чем думают тут в MS, я не в курсе.

S>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?


Кто сказал, что "всё прекрасно"? Я не знаю, откуда и как вы делаете такие выводы.
И что это "все", что делают чтобы отказаться? В основном делают там, где просто неудобно/дорого по другим причинам возлагать это на ОС.

Да, есть цена на переключение, даже вход и выход из системного вызова чего-то стоит, и на Windows эта цена сильно выше, чем на Linux. Может, потому для Windows и стараются сократить эти переключения. В Linux я не вижу такого активного стремления, если где-то такое делают, то в рантаймах со сложной собственной логикой и полностью "управляемым" выполнением (Go, Erlang).

N>>Не может быть квадратных кругов и круглых квадратов. Если с данными надо работать, то для этого нужен доступ к ним. Если нужен доступ, их надо прочитать из памяти и записать в память, с соответствующей ценой. Я бы предпочёл, чтобы компиляция хранила максимум в регистрах процессора, а что не помещается — максимум на стеке: не будет дурных потерь скорости. А если при этом само переключение формально дороже — тоже не страшно, записать компактно пару сотен байт и потом прочитать — эффективнее, чем размазывать их по десяткам кэш-строк.


S>Ну класс если используешь Value типы тоже будут компактно расположены, а не валуе типы что на стеке, что не на нем все равно в куче


То есть в дотнет ещё не завезли escape analysis? (сомневаюсь)

S>>>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.

N>>>>Тоже не понимаю, при чём тут эта реплика.
S>>> В том, что с использованием пула потоков сводит к минимуму переключение потоков

N>>Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен.

S>Вот ссылочка из предыдущего сообщения AsyncSemaphore

S>Если lock занят то возвращается Task и ожидается пока не вызовется SetResult

S>Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock

Почему это "нужно заменять"? Обычный lock настолько неэффективен?
The God is real, unless declared integer.
Re[10]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 03.07.21 07:31
Оценка:
Здравствуйте, Cyberax, Вы писали:

S>>>В таком случае зеленые потоки будут просто не нужны. Ну т.е. в ядре ОС выполнять код соотв. стороннего планировщика.

N>>И кто пустит посторонний код в ядро?
C>Можно eBPF использовать

Я не уверен, что на нём можно написать шедулер. Хотя если есть возможность оперирования с конкретными байтиками и всё уложить на эту логику, то может пойти...

N>>Похожие разработки как раз были. Во FreeBSD игрались с подходом "scheduler activations". Это выглядело так: если нить блокируется в ядре, ядро запускает указанный процессом пользовательский код с указанием "а подумай, не переключиться ли на что-то ещё". Пользовательский код управляет тем, какие из видимых ниток должны исполняться.

N>>Разработка шла приблизительно в 1999-2002. После отказались и сделали тупо 1:1, все нити процесса известны ядру и оно их переключает. Вот не справились со сложностью.
C>Проблема в том, что переключения контекстов уж слишком дорогие.

Проблема нового или старого решения? По крайней мере горизонтальное переключение в пределах одного процесса вроде бы дешёвое.
The God is real, unless declared integer.
Re: Горутины и потоки
От: GarryIV  
Дата: 03.07.21 08:21
Оценка:
Здравствуйте, mrTwister, Вы писали:

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


Как ты это себе видишь на уровне ОС? Убрать относительно дорогие потоки не получится, хотя бы потому что уже 100500 программ их используют. Плюс у горутин (и других аналогов) есть свои ограничения и недостатки в дополнение к потокам они норм вместо них нет. И чем реализация на уровне ОС будет лучше библиотечной?
WBR, Igor Evgrafov
Re: Горутины и потоки
От: ononim  
Дата: 03.07.21 09:48
Оценка:
T>А почему, собственно, потоки операционной системы не могут быть столь же эффективны, как и горутины? То есть почему разработчики OS тоже не могут сделать динамический стек и другие оптимизации?
Что касается динамического стека тут уже написали. Что касается "других оптимизаций", то поток — это прежде всего контекст процессора. Поток операционной системы — это два контекста процессора — юзермодный и кернелмодный, чтоб их оба переключить требуется переход в кернелмод и обратно. "Другие оптимизации" aka green threads — это лишь юзермодный контекст, соответственно потоком операционной системы быть не могут. Но библиотеки, позволяющие реализовать такую функциональность для unmanaged кода существуют — начиная от реальных зеленых потоков (хотя я эту либу не юзал никогда) и заканчивая корутинами в плюсах. В принципе как часть АПИ тоже есть как минимум пример — т.н. fiber в винде но особой популярности они не получили.
Как много веселых ребят, и все делают велосипед...
Отредактировано 03.07.2021 9:55 ononim . Предыдущая версия . Еще …
Отредактировано 03.07.2021 9:49 ononim . Предыдущая версия .
Re[13]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 03.07.21 11:45
Оценка:
Здравствуйте, netch80, Вы писали:


N>>>Да, проблема есть (хотя про "основной источник проблем с производительностью" тут загнули, случай он разный бывает). Но вы никак от этого не избавитесь ни шедулингом, ни переходом на await, пока вам потребуется ровно та же синхронизация.

S>> Я тебе привел пример аналога LockAsync. Поток ничего не ждет, а по событию запускается из пула потоков. Вот пример асинхронной очереди
S>>AsyncProducerConsumerCollection
S>>Просто привел пример с использованием ValueTask вместо Task

N>Так с точки зрения ОС что поможет тут осознать, когда можно переключать на другую нить вообще другого процесса, а когда лучше этого не делать, потому что захвачен важный лок (и даже в случае асинхронных локов это блокирует другие задачи)?

N>Я б ожидал каких-то явных системных вызовов, но тут про них ни слова.
Ту тут главное tcs.TrySetResult(item);
И фцфше может продолжаться в этом же потоке в зависимости от условий создания TaskCompletionSource
http://rsdn.org/forum/dotnet/7639144.1
Автор: RushDevion
Дата: 20.01.20

var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);

https://docs.microsoft.com/ru-ru/dotnet/api/system.threading.tasks.taskcreationoptions?view=net-5.0#System_Threading_Tasks_TaskCreationOptions_RunContinuationsAsynchronously

N>>>Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора.

S>> Угу который при переходе на новый поток сбрасывается.

N>Кто сбрасывается? Кэш процессора? Зачем? Даже с мерами против Meltdown/Spectre кэш сбрасывается при переключении между процессами, а не между нитками одного процесса. Хотя о чём и, главное, чем думают тут в MS, я не в курсе.


Ну кэш то первого уровня точто сбросится ибо слишком мал. И все зависит от используемых данных потока.

S>>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?


N>Кто сказал, что "всё прекрасно"? Я не знаю, откуда и как вы делаете такие выводы.

N>И что это "все", что делают чтобы отказаться? В основном делают там, где просто неудобно/дорого по другим причинам возлагать это на ОС.
Асинхронность и производительность
Утверждают, что выхлоп есть

N>Да, есть цена на переключение, даже вход и выход из системного вызова чего-то стоит, и на Windows эта цена сильно выше, чем на Linux. Может, потому для Windows и стараются сократить эти переключения. В Linux я не вижу такого активного стремления, если где-то такое делают, то в рантаймах со сложной собственной логикой и полностью "управляемым" выполнением (Go, Erlang).


Ну сейчас практически все сервера в облаке на линуксе, но там же и asinc await используются на прополуюю ибо основная то работа это запрос к базе данных и прочие асинхронные операции

N>>>Не может быть квадратных кругов и круглых квадратов. Если с данными надо работать, то для этого нужен доступ к ним. Если нужен доступ, их надо прочитать из памяти и записать в память, с соответствующей ценой. Я бы предпочёл, чтобы компиляция хранила максимум в регистрах процессора, а что не помещается — максимум на стеке: не будет дурных потерь скорости. А если при этом само переключение формально дороже — тоже не страшно, записать компактно пару сотен байт и потом прочитать — эффективнее, чем размазывать их по десяткам кэш-строк.


S>>Ну класс если используешь Value типы тоже будут компактно расположены, а не валуе типы что на стеке, что не на нем все равно в куче


N>То есть в дотнет ещё не завезли escape analysis? (сомневаюсь)


S>>>>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.

N>>>>>Тоже не понимаю, при чём тут эта реплика.
S>>>> В том, что с использованием пула потоков сводит к минимуму переключение потоков

N>>>Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен.

S>>Вот ссылочка из предыдущего сообщения AsyncSemaphore

S>>Если lock занят то возвращается Task и ожидается пока не вызовется SetResult

S>>Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock

N>Почему это "нужно заменять"? Обычный lock настолько неэффективен?

Обычный lock может быть долгим и ожидать его могут тысячи потоков.
Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?
и солнце б утром не вставало, когда бы не было меня
Re[14]: Горутины и потоки
От: Sharov Россия  
Дата: 03.07.21 12:13
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?


Для типизации тасков/промисов .
Кодом людям нужно помогать!
Re[15]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 03.07.21 14:22
Оценка:
Здравствуйте, Sharov, Вы писали:

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


S>>Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?


S>Для типизации тасков/промисов .


Тут речь про потоки и бесплатность их переключения. По сути раньше синхронный код и использованием ожиданием эвента при вызове асинхронной операции была нормальной практикой.
Но было сказано, что это отстой ибо есть большие затраты на переключение потока. Поэтому лучше использовать пул потока с минимумом переключений.
Но в этой ветке утверждают, что это все херня ибо все очень быстро и использование потоков и хэндлов не проблема и стоимость переключения минимальны.
и солнце б утром не вставало, когда бы не было меня
Re[16]: Горутины и потоки
От: Sharov Россия  
Дата: 04.07.21 00:13
Оценка:
Здравствуйте, Serginio1, Вы писали:
S>>Для типизации тасков/промисов .

S> Тут речь про потоки и бесплатность их переключения. По сути раньше синхронный код и использованием ожиданием эвента при вызове асинхронной операции была нормальной практикой.

S>Но было сказано, что это отстой ибо есть большие затраты на переключение потока.

Вероятно это связано с многоядерностью ... Хотя не факт -- ведь в чем разница для async\await от ядерности?
Т.е. мы четко типизируем "выполни это после этого (io операции какой-нибудь)" и нам ядерыные объекты синхронизации
тут ни к чему.

S>Поэтому лучше использовать пул потока с минимумом переключений.


Пул потоков это про создание потоков, а не про переключение.
Кодом людям нужно помогать!
Re[14]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 04.07.21 05:18
Оценка: 12 (1)
Здравствуйте, Serginio1, Вы писали:

N>>Так с точки зрения ОС что поможет тут осознать, когда можно переключать на другую нить вообще другого процесса, а когда лучше этого не делать, потому что захвачен важный лок (и даже в случае асинхронных локов это блокирует другие задачи)?

N>>Я б ожидал каких-то явных системных вызовов, но тут про них ни слова.
S>Ту тут главное tcs.TrySetResult(item);
S>И фцфше может продолжаться в этом же потоке в зависимости от условий создания TaskCompletionSource

Как по мне, это мало того, что имеет очень слабое отношение к теме дискуссии, так и вообще какое-то кошмарное безумие. На чём тут дедлочиться??
Ну, стал родитель в ожидание. Пошёл запускаться код из вложенной задачи. Ну поспал (Thread.Sleep асинхронный?) Поставил результат. Родитель проснулся. Что может пойти не так? Что за бардак там в дотнете, или C#, кто это строит?

Я вот хотел сделать (на Core 5.0) чтобы коллбэки на чтение сокета срабатывали всегда в нужной нитке. Не получается: мне их, фактически, принудительно загоняют в какой-то пул, который я не просил создавать, и я должен ещё думать о мьютексах, чтобы применить их данные. А тут, наоборот, какая-то самоблокировка на одной нитке непонятно на чём.

И при чём тут какой-то TrySetResult? Try на ожидание — я понимаю, а на SetResult — нет. Мы заданием результата для Task снижаем "напряжение" в системе, сокращая количество ожидаемых сущностей и сокращая contention за ресурсы (включая ожидания), тогда какой нафиг Try?

N>>Кто сбрасывается? Кэш процессора? Зачем? Даже с мерами против Meltdown/Spectre кэш сбрасывается при переключении между процессами, а не между нитками одного процесса. Хотя о чём и, главное, чем думают тут в MS, я не в курсе.


S>Ну кэш то первого уровня точто сбросится ибо слишком мал. И все зависит от используемых данных потока.


Он не _сбросится_. Он _вымоется_. Разница принципиальна. И почему ты думаешь, что они будут использовать настолько разные код и данные?

Я вон выше уже упоминал: открыл сокет, повесил коллбэки на приём датаграмм. Послал две датаграммы. Один коллбэк вызвался в нитке 6, другой — в нитке 9 (дотнет нумерует). Зачем?

S>>>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?


N>>Кто сказал, что "всё прекрасно"? Я не знаю, откуда и как вы делаете такие выводы.

N>>И что это "все", что делают чтобы отказаться? В основном делают там, где просто неудобно/дорого по другим причинам возлагать это на ОС.
S>Асинхронность и производительность
S>Утверждают, что выхлоп есть

По сравнению с чем? Там сплошная Обстракция(tm) без конкретики.
Ну, повторюсь, на Windows треды дорогие (и создание, и переключение). Вероятно, по сравнению с этим, действительно, польза async-await очевидна.

N>>Да, есть цена на переключение, даже вход и выход из системного вызова чего-то стоит, и на Windows эта цена сильно выше, чем на Linux. Может, потому для Windows и стараются сократить эти переключения. В Linux я не вижу такого активного стремления, если где-то такое делают, то в рантаймах со сложной собственной логикой и полностью "управляемым" выполнением (Go, Erlang).


S> Ну сейчас практически все сервера в облаке на линуксе, но там же и asinc await используются на прополуюю ибо основная то работа это запрос к базе данных и прочие асинхронные операции


Возможно. А меряли результат по сравнению с другими подходами?

S>>>Если lock занят то возвращается Task и ожидается пока не вызовется SetResult

S>>>Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock

N>>Почему это "нужно заменять"? Обычный lock настолько неэффективен?

S>Обычный lock может быть долгим и ожидать его могут тысячи потоков.

Эээ...
1. Если у кого-то на "обычном локе" тысячи ниток, то тут в первую очередь нужно менять весь дизайн (может быть, даже архитектуру, в обычном понимании).
Любые локи предназначены для малого количества конкурентов за их ресурс. Даже по количеству хартов — это уже много. До 4 — нормально. Если выше — надо думать разделять на разные локи. Если выше 8 — срочно думать.
2. Я уже описывал работу адаптивных мьютексов. Вначале, да, цикл сколько-то попыток захвата в стиле спинлока — чистым CAS (если нет видимой очереди). Если они не получились, тогда уже начинать более серьёзную машину — или через await, или через Lock() в ядре.
Но противопоставлять спинлоки мьютексам и говорить про "эффективность" spinlock по сравнению с мьютексом — это просто смешно.
Спинлок заведомо менее эффективен на длительных периодах и большом количестве конкурентов. Есть случаи, когда его использование неизбежно — когда шедулера более высокого порядка просто нет (в первую очередь это борьба в ядре за общие ресурсы между разными хартами). Но в остальных случаях его использовать — только ухудшать.

S>Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?


1. "Во-первых, это красиво" (tm). Линейное написание кода в разы проще цепочек коллбэков, неважно, в явном виде или на промисах.
Если программист не справляется без await, он будет писать линейно, но будут сплошные блокировки на синхронных операциях. Await позволяет использовать широкие ресурсы кодеров с реальным улучшением характеристик именно за счёт этого — меньше блокировок.

2. При высокой цене переключения между нитками внутреннее переключение (шедулером на границе awaitʼа) будет в разы дешевле переключения через ОС.
The God is real, unless declared integer.
Re[16]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 04.07.21 05:18
Оценка: +1
Здравствуйте, Serginio1, Вы писали:

S>Но было сказано, что это отстой ибо есть большие затраты на переключение потока. Поэтому лучше использовать пул потока с минимумом переключений.

S> Но в этой ветке утверждают, что это все херня ибо все очень быстро и использование потоков и хэндлов не проблема и стоимость переключения минимальны.

Если это "утверждают" про меня, я не говорю, что "херня", "стоимость переключения минимальна" и т.п. — я, наоборот, утверждаю, что для Windows она весьма высока (сам не мерял, данные "из интернетов"), и использование всех этих фокусов там может быть очень оправдано. Но — описываемые тобой цели типа "не переключать пока держится лок" автоматически этим не реализуются, и могут быть вместо этого только ухудшены, если нет явной подсказки ядру.
The God is real, unless declared integer.
Re[17]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 04.07.21 05:19
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Вероятно это связано с многоядерностью ... Хотя не факт -- ведь в чем разница для async\await от ядерности?


Внутреннему шедулеру придётся это учитывать, но и работать в сумме может быстрее.

S>>Поэтому лучше использовать пул потока с минимумом переключений.


S>Пул потоков это про создание потоков, а не про переключение.


Про переключение тоже — их ведь больше одного
The God is real, unless declared integer.
Re[15]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 05.07.21 07:57
Оценка:
Здравствуйте, netch80, Вы писали:

N>Эээ...

N>1. Если у кого-то на "обычном локе" тысячи ниток, то тут в первую очередь нужно менять весь дизайн (может быть, даже архитектуру, в обычном понимании).
N>Любые локи предназначены для малого количества конкурентов за их ресурс. Даже по количеству хартов — это уже много. До 4 — нормально. Если выше — надо думать разделять на разные локи. Если выше 8 — срочно думать.
N>2. Я уже описывал работу адаптивных мьютексов. Вначале, да, цикл сколько-то попыток захвата в стиле спинлока — чистым CAS (если нет видимой очереди). Если они не получились, тогда уже начинать более серьёзную машину — или через await, или через Lock() в ядре.
N>Но противопоставлять спинлоки мьютексам и говорить про "эффективность" spinlock по сравнению с мьютексом — это просто смешно.
N>Спинлок заведомо менее эффективен на длительных периодах и большом количестве конкурентов. Есть случаи, когда его использование неизбежно — когда шедулера более высокого порядка просто нет (в первую очередь это борьба в ядре за общие ресурсы между разными хартами). Но в остальных случаях его использовать — только ухудшать.

S>>Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?


N>1. "Во-первых, это красиво" (tm). Линейное написание кода в разы проще цепочек коллбэков, неважно, в явном виде или на промисах.

N>Если программист не справляется без await, он будет писать линейно, но будут сплошные блокировки на синхронных операциях. Await позволяет использовать широкие ресурсы кодеров с реальным улучшением характеристик именно за счёт этого — меньше блокировок.
+
N>2. При высокой цене переключения между нитками внутреннее переключение (шедулером на границе awaitʼа) будет в разы дешевле переключения через ОС.
Вот спасибо! Все таки дешевле!
Ну давай возьмем типичный сервер. БОльшая часть задач сводится к вызову асинхронным методов а именно запрос к базе данных (не локальной), запрос к сайту, к файлу итд.
Выделять для каждого запроса отдельный поток не имеет смысла (тут и затраты на создание потока или держать пул огромного размера).
Как правило эти задачи небольшие.
Отдельный поток стоит выделять для длительных задач LongRunning

То есть смысла в выделении потоках на задачу нет. Есть смысл использовать пул потоков. Что было кстати в ранних версиях вэб сервисов ASP.Net.
Тогда тоже были и асинхронные делегаты (BeginInvoke() и EndInvoke()) и ThreadPool. Но в том же сервисе вызов вэб метода был синхронным, то есть привязан к одному потоку. То есть внутри то можно было использовать калбеки и прочее, но поток морозился на эвенте

То есть например когда мы в базе данных что то активно меняем, и запросов на чтение как раз и будут ждать пока не закончится запись.
Конечно можно по максимуму съузить блокировки, но не всегда это возможно.
и солнце б утром не вставало, когда бы не было меня
Re[15]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 05.07.21 08:08
Оценка: 6 (1)
Здравствуйте, netch80, Вы писали:


N>Я вот хотел сделать (на Core 5.0) чтобы коллбэки на чтение сокета срабатывали всегда в нужной нитке. Не получается: мне их, фактически, принудительно загоняют в какой-то пул, который я не просил создавать, и я должен ещё думать о мьютексах, чтобы применить их данные. А тут, наоборот, какая-то самоблокировка на одной нитке непонятно на чём.


Ну тебе в данной нитке нужно организовать очередь и пихать в неё свои калбеки. Поток опять же ставить на ожидантие и при постановке в очередь выставлять эвент в сигнальное состояние.
Либо использовать контекст синхронизации
https://docs.microsoft.com/ru-ru/dotnet/standard/parallel-programming/how-to-specify-a-task-scheduler-in-a-dataflow-block

Кстати про шедулер и сравнением с GO
https://habr.com/ru/post/336000/
и солнце б утром не вставало, когда бы не было меня
Re[16]: Горутины и потоки
От: Sharov Россия  
Дата: 05.07.21 10:55
Оценка:
Здравствуйте, Serginio1, Вы писали:


N>>Я вот хотел сделать (на Core 5.0) чтобы коллбэки на чтение сокета срабатывали всегда в нужной нитке. Не получается: мне их, фактически, принудительно загоняют в какой-то пул, который я не просил создавать, и я должен ещё думать о мьютексах, чтобы применить их данные. А тут, наоборот, какая-то самоблокировка на одной нитке непонятно на чём.

S> Ну тебе в данной нитке нужно организовать очередь и пихать в неё свои калбеки. Поток опять же ставить на ожидантие и при постановке в очередь выставлять эвент в сигнальное состояние.

Кстати да, не проще ли все организовать через блокирующую коллекцию? Пишет кто хочет, читает один поток.

S>Либо использовать контекст синхронизации

S>https://docs.microsoft.com/ru-ru/dotnet/standard/parallel-programming/how-to-specify-a-task-scheduler-in-a-dataflow-block

Интересная идея, но тут скорее выгода, если рабочих потоков может быть больше одного, а для одного городить огород
смысла нет. Хотя для UI сгородили.
Кодом людям нужно помогать!
Re[17]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 05.07.21 14:05
Оценка:
Здравствуйте, Sharov, Вы писали:

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



N>>>Я вот хотел сделать (на Core 5.0) чтобы коллбэки на чтение сокета срабатывали всегда в нужной нитке. Не получается: мне их, фактически, принудительно загоняют в какой-то пул, который я не просил создавать, и я должен ещё думать о мьютексах, чтобы применить их данные. А тут, наоборот, какая-то самоблокировка на одной нитке непонятно на чём.

S>> Ну тебе в данной нитке нужно организовать очередь и пихать в неё свои калбеки. Поток опять же ставить на ожидантие и при постановке в очередь выставлять эвент в сигнальное состояние.

S>Кстати да, не проще ли все организовать через блокирующую коллекцию? Пишет кто хочет, читает один поток.


S>>Либо использовать контекст синхронизации

S>>https://docs.microsoft.com/ru-ru/dotnet/standard/parallel-programming/how-to-specify-a-task-scheduler-in-a-dataflow-block

S>Интересная идея, но тут скорее выгода, если рабочих потоков может быть больше одного, а для одного городить огород

S>смысла нет. Хотя для UI сгородили.
Ну так разговор то о том, что нужно выполнить калбек в определенном потоке.
Ну организовать то свою очередь сообщений не сложно, тот же эвент WaitOne с таймаутом на всякий случай.
Контекс синхоронизации это для ленивых. Ты можешь его сам создать
if (SynchronizationContext.Current == null)

            SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());

или System.Windows.Threading.DispatcherSynchronizationContext
и солнце б утром не вставало, когда бы не было меня
Отредактировано 05.07.2021 15:38 Serginio1 . Предыдущая версия .
Re[16]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 06.07.21 08:39
Оценка:
Здравствуйте, Serginio1, Вы писали:

N>>2. При высокой цене переключения между нитками внутреннее переключение (шедулером на границе awaitʼа) будет в разы дешевле переключения через ОС.

S> Вот спасибо! Все таки дешевле!

Ну так я никогда не возражал, что при описанном условии (дорогом переключении) лучше его обходить.
Но мне, к счастью, не приходится работать с Windows.
И то, что я говорил, не сводится только к цене переключения (а ты почему-то остальные темы обходишь).

S> Ну давай возьмем типичный сервер. БОльшая часть задач сводится к вызову асинхронным методов а именно запрос к базе данных (не локальной), запрос к сайту, к файлу итд.

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

Пока кэпствуешь, ok.

S> То есть смысла в выделении потоках на задачу нет. Есть смысл использовать пул потоков. Что было кстати в ранних версиях вэб сервисов ASP.Net.

S>Тогда тоже были и асинхронные делегаты (BeginInvoke() и EndInvoke()) и ThreadPool. Но в том же сервисе вызов вэб метода был синхронным, то есть привязан к одному потоку. То есть внутри то можно было использовать калбеки и прочее, но поток морозился на эвенте

Эти все подробности не знаю и они мне как-то побоку.

S>То есть например когда мы в базе данных что то активно меняем, и запросов на чтение как раз и будут ждать пока не закончится запись.

S>Конечно можно по максимуму съузить блокировки, но не всегда это возможно.

Так какое отношение это всё имеет, например, к качеству управления локами?
The God is real, unless declared integer.
Re[16]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 06.07.21 08:51
Оценка:
Здравствуйте, Serginio1, Вы писали:

N>>Я вот хотел сделать (на Core 5.0) чтобы коллбэки на чтение сокета срабатывали всегда в нужной нитке. Не получается: мне их, фактически, принудительно загоняют в какой-то пул, который я не просил создавать, и я должен ещё думать о мьютексах, чтобы применить их данные. А тут, наоборот, какая-то самоблокировка на одной нитке непонятно на чём.


S> Ну тебе в данной нитке нужно организовать очередь и пихать в неё свои калбеки. Поток опять же ставить на ожидантие и при постановке в очередь выставлять эвент в сигнальное состояние.

S>Либо использовать контекст синхронизации
S>https://docs.microsoft.com/ru-ru/dotnet/standard/parallel-programming/how-to-specify-a-task-scheduler-in-a-dataflow-block

https://www.youtube.com/watch?v=ub3VSgjOIw8&amp;t=16
Честно, я совсем не понимаю, зачем вначале конструировать бешеного быка, чтобы потом искать методы его успокоения, когда надо проехаться на автобусе с таким "двигателем" под капотом.

S>Кстати про шедулер и сравнением с GO

S>https://habr.com/ru/post/336000/

Ага, ага. Вы это серьёзно вместе с автором статьи?
Первый же пример

>> return 1 + await CountRecursivelyAsync(count — 1);

>> Консоль упадет со StackOverflowException. Печаль!

Какой нафиг StackOverflowException, откуда тут стек взялся? Или это специфика того, что компилятор делает из awaitʼа рекурсивного вызова той же функции? "Ну нафиг" (tm).
Тут не шедулеры надо сравнивать, а вначале этот бред исправлять.
The God is real, unless declared integer.
Re[17]: Горутины и потоки
От: Sharov Россия  
Дата: 06.07.21 09:42
Оценка:
Здравствуйте, netch80, Вы писали:

S>>https://habr.com/ru/post/336000/

N>Ага, ага. Вы это серьёзно вместе с автором статьи?
N>Первый же пример
>>> return 1 + await CountRecursivelyAsync(count — 1);
>>> Консоль упадет со StackOverflowException. Печаль!
N>Какой нафиг StackOverflowException, откуда тут стек взялся? Или это специфика того, что компилятор делает из awaitʼа рекурсивного вызова той же функции? "Ну нафиг" (tm).

1)Что значит откуда стек взялся в контексте потоков? Правильно было бы сказать откуда StackOverflowException в контексте потоков, а не стек.
2)Скорее всего код выполнялся синхронно, т.е. в одном потоке. В .net есть оптимизация, которая при отдаче
управления(потока) проверяет "а не завершилась ли задача", и если да, то в том же потоке запустит продолжение,
т.е. рекурсивный вызов. Отсюда и исключение.
Кодом людям нужно помогать!
Re[17]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 06.07.21 13:55
Оценка:
Здравствуйте, netch80, Вы писали:

S>>Конечно можно по максимуму съузить блокировки, но не всегда это возможно.


N>Так какое отношение это всё имеет, например, к качеству управления локами?

А их как таковых и нет если использовать задачи. Реальными блокировками уже занимается SQL он же дает отлуп если занят
и солнце б утром не вставало, когда бы не было меня
Re[17]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 06.07.21 13:58
Оценка:
Здравствуйте, netch80, Вы писали:


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


То есть у тебя проблема создать поток а в нем организовать очередь и её выборку это бешенный бык) там 15 строчек кода то всего.
Про контекст синхронизации это для ленивых которым влом 15 строчек когда набрать
и солнце б утром не вставало, когда бы не было меня
Re[2]: Горутины и потоки
От: fk0 Россия https://fk0.name
Дата: 28.11.21 14:12
Оценка:
Здравствуйте, gandjustas, Вы писали:

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


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


А у Go -- бесплатное?

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


Адресного пространства, а не стека. Памяти на стек тратится, минимум, килобайт 8 думаю.

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


Можно подумать, Go знает. Часто этого и сам поток не знает.

G>В Винде уже давно есть возможность создавать пулы потоков, очереди задач, ожидания IO и таймеров, что позволяет иметь много потоков в программе при минимуме потоков в ОС.


В Linux тоже make_context и switch_context были с доисторических времён. Когда ещё потоков наверное не было.
Re[3]: Горутины и потоки
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 28.11.21 18:35
Оценка: +1
Здравствуйте, fk0, Вы писали:

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


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


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

fk0> А у Go -- бесплатное?
Гораздо более дешевое, чем в ОС, примерно на два десятичных порядка.



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

fk0> Адресного пространства, а не стека. Памяти на стек тратится, минимум, килобайт 8 думаю.
Сколько реально тратится — зависит от того как написан код. Может оказаться довольно много в сумме.
Когда еще не было async\await в C# я переписал один сервер с использования потока на соединение на асинхронные вызовы и очередь. Получил экономию около 200мб, что составляло четверть расходуемой памяти.


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

fk0> Можно подумать, Go знает. Часто этого и сам поток не знает.
Конечно знает. Я не знаю как точно в Go это устроено, но компилятор C# прекрасно определяет какие перменные нужны в продолжении.

G>>В Винде уже давно есть возможность создавать пулы потоков, очереди задач, ожидания IO и таймеров, что позволяет иметь много потоков в программе при минимуме потоков в ОС.


fk0> В Linux тоже make_context и switch_context были с доисторических времён. Когда ещё потоков наверное не было.

Это типа cooperative multitasking? Увы в рукопашную его нигде практически не используют.
Отредактировано 29.11.2021 8:37 gandjustas . Предыдущая версия .
Re: Горутины и потоки
От: scf  
Дата: 02.12.21 13:03
Оценка:
Здравствуйте, mrTwister, Вы писали:

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


Горутины, как и другие средства асинхронного программирования — это кооперативная многозадачность, не вытесняющая. Это существенно быстрее, но плохо написанный "поток" может залочить систему целиком.
Re[2]: Горутины и потоки
От: mrTwister Россия  
Дата: 02.12.21 14:29
Оценка:
Здравствуйте, scf, Вы писали:

scf>Горутины, как и другие средства асинхронного программирования — это кооперативная многозадачность, не вытесняющая. Это существенно быстрее, но плохо написанный "поток" может залочить систему целиком.


Нет, горутины — это давно уже вытесняющая многозадачность, так как переключение контекста происходит по внешнему таймеру независимо от воли переключаемой горутины (кроме некоторых выделенных случаев типа выполнения системного вызова и др.)
лэт ми спик фром май харт
Re[3]: Горутины и потоки
От: scf  
Дата: 02.12.21 14:38
Оценка:
Здравствуйте, mrTwister, Вы писали:

T>Нет, горутины — это давно уже вытесняющая многозадачность, так как переключение контекста происходит по внешнему таймеру независимо от воли переключаемой горутины (кроме некоторых выделенных случаев типа выполнения системного вызова и др.)


Можно ссылку? Не смог нагуглить сходу, пишут "The Go Runtime Scheduler does cooperative scheduling,"
Re[4]: Горутины и потоки
От: mrTwister Россия  
Дата: 02.12.21 14:42
Оценка:
Здравствуйте, scf, Вы писали:

scf>Можно ссылку? Не смог нагуглить сходу, пишут "The Go Runtime Scheduler does cooperative scheduling,"


https://github.com/golang/go/issues/24543
лэт ми спик фром май харт
Re: Горутины и потоки
От: SkyDance Земля  
Дата: 02.12.21 15:37
Оценка:
T>А почему, собственно, потоки операционной системы не могут быть столь же эффективны, как и горутины?

Потому что они обеспечивают изоляцию другого порядка.

Если что, горутины — это бледная пародия на модель выполнения Erlang, полученная в силу NIH-синдрома и неумения или нежелания разбираться в уже существующих концепциях. Чтобы понять более фундаментальные основы, и то, как оно, надеюсь, будет в итоге доступно большинству, рекомендую взглянуть как раз на Erlang.
Re[2]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 02.12.21 15:49
Оценка:
Здравствуйте, SkyDance, Вы писали:

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


SD>Потому что они обеспечивают изоляцию другого порядка.


SD>Если что, горутины — это бледная пародия на модель выполнения Erlang, полученная в силу NIH-синдрома и неумения или нежелания разбираться в уже существующих концепциях. Чтобы понять более фундаментальные основы, и то, как оно, надеюсь, будет в итоге доступно большинству, рекомендую взглянуть как раз на Erlang.


По сообщению уже можно было понять, кто его написал, не смотря в заголовки

А как по-вашему — Тони Хоар писал свой фундаментальный труд уже подсмотрев Erlang через машину времени и написав бледную пародию, или всё-таки кто на ком стоял — оказалось наоборот?
The God is real, unless declared integer.
Re[5]: Горутины и потоки
От: scf  
Дата: 02.12.21 16:55
Оценка:
Здравствуйте, mrTwister, Вы писали:

T>https://github.com/golang/go/issues/24543


Спасибо, интересная реализация. Но я не смог найти сравнения быстродействия обоих подходов, вы не видели?
Re[3]: Горутины и потоки
От: SkyDance Земля  
Дата: 02.12.21 21:46
Оценка: :))
N>А как по-вашему — Тони Хоар писал свой фундаментальный труд уже подсмотрев Erlang через машину времени и написав бледную пародию, или всё-таки кто на ком стоял — оказалось наоборот?

Сэр Чарльз, боюсь, не был среди авторов велосипеда "горутина".

Он предложил математическую модель "акторов". Теоретические основы. Которые на практике были воплощены и до Erlang'а, скажем, в LISP, частично в Smalltalk. Но как afterthought. В отличие от Erlang, который был как раз спроектирован вокруг данной концепции. Поэтому программы, которые хорошо ложатся на эту модель, выглядят очень кратко, и, не побоюсь этого слова, красиво.

Go же идет "от сохи", с ушами привычной императивщины отовсюду. Что, конечно, проще преподать как некое "усовершенствование всего того, что вы уже знаете". Знакомый синтаксис, старые баги, и вообще все то, к чему мы с детства, с Бейсика или Паскаля, привыкли. Та самая "более быстрая лошадь", которую Генри Форд не хотел давать своим покупателям.

Это и ведет к тяжеловесности и отсутствии красоты в коде. Что мешает разобраться в фундаментальных основах. Половинчатые решения вроде "хотите message passing, хотите — shared memory" только затрудняют понимание концепций. С учетом того, насколько позже Go появился, его вторичность очевидна. Именно что "еще один язык в куче других одинаковых".
Re[11]: Горутины и потоки
От: lpd Черногория  
Дата: 02.12.21 22:00
Оценка:
Здравствуйте, netch80, Вы писали:

N>>>И кто пустит посторонний код в ядро?

C>>Можно eBPF использовать

N>Я не уверен, что на нём можно написать шедулер. Хотя если есть возможность оперирования с конкретными байтиками и всё уложить на эту логику, то может пойти...


ebpf в планировщике
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.