Re[53]: Зачем Майкрософту рубить сук, на котором он сидит?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 19.05.14 06:53
Оценка: 2 (1)
Здравствуйте, Sinix, Вы писали:

N>>См. мой предыдущий ответ. С тасками названного тобой образца заполнение всех нитей ожиданием I/O заблокирует пул в целом.

S>Неа, 1 поток связан с кучей IOCP,

Я сразу перебью тут — _как_? Насколько я вижу по докам, GetQueuedCompletionStatus[Ex] позволяет ждать события одновременно только с одного IOCP. Вариант "перебирать все в цикле" без ожидания, понятно, не устраивает.

Вообще, перечитав доку, я перестал понимать логику организации IOCP. В его юниксовых аналогах, привычных мне (kqueue, epoll) операция добавления ожидания к конкретному экземпляру IOCP выполняется явно, исключения — допустима и тоже явно, и есть явная заточка дизайна на 1 IOCP (kqueue, etc.) на нить. В случае виндового IOCP мы имеем CreateIoCompletionPort(), которая явно рассчитана на создание персонального IOCP каждому хэндлу (вариант присербиться к готовому выглядит как добавленный потом и сбоку), а убрать связь хэндла с IOCP нельзя вообще. Перебросить на другой — вероятно, тоже. То ли доки тут откровенно что-то недоговаривают, то ли этот механизм очень негибкий. (Про возможность асинхронных операций без явного IOCP я вообще не вспоминаю.)

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


Это если ты явно, сидя в задаче под TPL, заказываешь продолжение. Если же ты пытаешься делать блокирующую операцию (например, неявно, вследствие вызова библиотечной функции), нить будет ждать. Это мы с НС уже выяснили.

S>Чему там блокироваться? И при чём тут кооперативная многозадачность? Напомню, ув Ikemefula вторую страницу рассказывает, как по его мнению устроены таски, исходя из убеждения, что таски === зелёные потоки, только плохо написанные.


Ну вот я и пытался выяснить, что же именно он хочет сказать.
А "чему блокироваться" — повторяю: вызывая написанный не тобой код, ты в общем случае не знаешь, на что он способен и почему. Более того, есть обычные (хотя игнорируемые) случаи такой блокировки даже с твоим на 146% кодом, начиная с page fault при его исполнении. Да, с таким явным образом что-то сделать нельзя (частично можно оптимизировать работу VM через madvise() с аналогами), можно полагаться на статистические характеристики. Или можно явно это использовать — в Unix до появления sendfile() было стандартным приёмом в отдельной нити замапить файл в память и делать длинный write() в сокет; в отдельной — потому что страничные фолты блокируют нить.
Но даже без таких случаев можно нарваться на блокировки в совершенно неожиданном месте.

S>Тут путаница Task.Wait() — это не ожидание, это блокировка текущего потока до завершения задачи. Чтобы продолжить выполнение асинхронно, по завершению задачи используется Task.ContinueWith() или await. В этом случае никаких блокировок не будет.


await дружит с TPL? При входе в ожидание по await движок TPL способен переключить на другую задачу?

S>Если речь про ожидание завершения внутри самой задачи, то как правило оно перекидывается на само API фреймфорка (которое в итоге работает через всё те же IOCP). Другими словами: если у класса есть async-API, то он не должен его реализовывать через блокирующие операции. Именно по той причине, что ты написал выше.


Ну опять же НС уже написал — что "внутре" там работа с IOCP через BeginXXX(), которые unmanaged. Что достаточно обидно. Вынести доступ к IOCP на верхний уровень могло дать результат не хуже.

N>>Простите, а с каких это пор неиспользование возможности _отладчика_ для "тасков" означает неработу с ними "по сути"?

S>Task list + Stacks Window — первое, что упоминается в документации по отладке для тасков. Ну и в студии в режиме отладки Task list — на самом видном месте, не заметить сложно.

Ты не понял. Я не про то, что если использовать отладчик, то в нём использовать соответствующие средства. Я про само использование отладчика. Для моих задач, например, такое средство непригодно (отладка может идти только через debug print). Но это не мешает использовать полный комплект средств, аналогичных тем, что тут обсуждаются: тут и FSM, и IOCP (в виде epoll), и пулы нитей, исполняющих задачи. Соответственно и мой интерес это "а что новенького придумали коллеги из другого лагеря?"

S>Проблема в том, что у Ikemefula по ветке полно очень странных утверждений: с одной стороны уверенность заоблачная и все возражения тупо отметаются, с другой — что ни фраза, то пальцем в небо.

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

См. мой предыдущий абзац. Ты постановил отладка == использование инструмента "отладчик". Верю, что в случае GUI это вполне допустимо. Но все ли задачи, даже в пределах .NET + async/await, это GUI? Я не соглашаюсь с ним, что "таски нельзя отладить" без уточнения деталей, как он их использует. Но твоё возражение не менее некорректно.
The God is real, unless declared integer.
Re[68]: Зачем Майкрософту рубить сук, на которых он сидит?
От: Ночной Смотрящий Россия  
Дата: 19.05.14 08:24
Оценка:
Здравствуйте, netch80, Вы писали:

N>>>Если да, то сейчас самое интересное мне это можно ли оба механизма (что TPL, что async/await) регулировать: какая группа нитей их исполняет; какое управление пулом, особенно в маргинальных случаях типа "все нити заняты, а надо ещё что-то делать";

НС>>Да.
N>Что именно "да"? Я тут задал минимум два вопроса.

Можно регулировать в определенной степени. Подробности, извини, пересказывать не буду — это есть в МСДН.

N>>>Я про то, что покрывает объяснение, а не выполнение

НС>>Зачем в объяснении общих механизмов включать переменную специфику?
N>Так ты ж уже начал её рассказывать

Нет.

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


Так и есть.

N> И тут оказывается, что в простейшем примере оба(!) два запущенных async-метода выполняются в отдельных нитях


Именно это я и хотел продемонстрировать. Потому что в примере демонстрируются не продолжения, а именно TPL и то как он работает с IOCP. А вот почему ты из специально написанного для этого примера хочешь получить характеристики совсем другого механизма — мне непонятно.
Если ты хочешь увидеть преобразование кода и кооперативную многозадачность на базе этого — нужен другой пример.

N>Если бы ты сказал "компилятор имеет право разрезать, а имеет — просто вынести в отдельные нити, в пул, etc. и ждать результата через стандартную синхронизацию;


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

N>>>Ну что-то он таки делает.

НС>>В консольных приложениях — ничего.
N>В чём тут принципиальность отличия консольных приложений от других?

Разные контексты в TLS по умолчанию.

N>В консольных не может быть асинхронных событий?


В консольных нет ограничений по взаимодействию потоков с основным.

N>С другой стороны, чем не-консольные приложения (а какие? flat GUI? WPF? что-то иное?) отличаются?


Конструкторы винформсных и wpf контролов устанавливают специальный контекст синхронизации в TLS.

N> Там можно реагировать через K секунд на нажатие кнопки?


Там нельзя трогать кнопки из потоков, отличных от основного. Защита от дурака.

НС>>http://blogs.msdn.com/b/visualstudio/archive/2011/02/07/8-part-async-series-by-eric-lippert.aspx


N>Вода мутная, одна штука.


Это у Липперта то мутная вода? Тебе не угодишь. Вообще то это он пишет на редкость понятно, просто у тебя, скорее всего, не хватает базы для шарпа.

N>Это ты про TPL. А про async/await?


А async/await — довольно примитивная и детерминированная штука, никакого интеллекта там нет.

N>>>Как привязывается? Async метод наследует TLS вызывающего?

НС>>Да. Либо можно явно привязать/отвязать.
N>Явно — это неинтересно, а вот наследование TLS при возможности реального исполнения в другой нити... что-то в этом есть парадоксальное.

На практике вполне нормально. И применялось задолго до появления TPL, механизм контекстов синхронизации с версии 2.0 присутствует.

N>Ага, вот и оно. То есть старые методы, которые вроде бы deprecated


Кто сказал, что они deprecated?

N>, никуда не делись и их просто завернули в обёртки. Спасибо.


Я именно об этом с самого начала пишу — для IO bound используются совершенно обычные IOCP.

N>Тогда ещё один вопрос. Зачем ко всяким BeginRead, BeginWrite обязательно парные EndRead, EndWrite и так далее, а не просто EndIO?


Чтобы почистить неуправляемые ресурсы, если они там есть. Например чтобы позвать CloseHandle для хендла IOCP.

НС>>BeginGetResponse — unmanaged метод, создает IOCP порт на сокет

N> ~~~~~~~~~
N>О'кей, тут механика понятна.

Он вполне может быть и managed, это ничего принципиально не меняет.

N>Жаль, что работа с IOCP сделана unmanaged.


Еще раз — это совершенно необязательно. Ты точно так же и с тем же результатом можешь позвать CreateIoCompletionPort из managed кода. Unmanaged тут исключительно для перформанса применен. Вот реализация из Моно, чисто managed — https://github.com/mono/mono/blob/master/mcs/class/System/System.Net/HttpWebRequest.cs.
Re[54]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Ночной Смотрящий Россия  
Дата: 19.05.14 08:34
Оценка:
Здравствуйте, netch80, Вы писали:

N>await дружит с TPL?


Он на него по умолчанию опирается.

N> При входе в ожидание по await движок TPL способен переключить на другую задачу?


"Вход в ожидание" не есть фича await, это как раз задача TPL. Так что ответ очевиден.

N>Ну опять же НС уже написал — что "внутре" там работа с IOCP через BeginXXX(), которые unmanaged.


Ничего подобного я не говорил. Я говорил строго про конкретный BeginGetResponse в МС реализации.
Re[54]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Sinix  
Дата: 19.05.14 08:57
Оценка:
Здравствуйте, netch80, Вы писали:

S>>Неа, 1 поток связан с кучей IOCP,

N>Я сразу перебью тут — _как_? Насколько я вижу по докам, GetQueuedCompletionStatus[Ex] позволяет ждать события одновременно только с одного IOCP. Вариант "перебирать все в цикле" без ожидания, понятно, не устраивает.

Что-то я не то с утра ляпнул

Разбирался в реализации под .net года три назад, так скорее всего могу врать. Емнип — создаём для каждого потока в IOCP-пуле по одному порту + словарь <ключ-задача>, поток ждёт в лупе GetQueuedCompletionStatus, получает ключ, достаёт из словаря задачу, запускает на выполнение.

N>Вообще, перечитав доку, я перестал понимать логику организации IOCP. В его юниксовых аналогах, привычных мне (kqueue, epoll) операция добавления ожидания к конкретному экземпляру IOCP выполняется явно, исключения — допустима и тоже явно, и есть явная заточка дизайна на 1 IOCP (kqueue, etc.) на нить.


Вполне возможно. Поскольку мне низкоуровневые детали понадобятся только, если я соберусь писать свой iocp-пул с нуля, детально в нативном API IOCP не копался.
Если рассматривать с точки зрения api дотнета — "оно просто работает". Фиговый ответ конечно, но поднимать матчасть сейчас времени нет, сорри


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

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


N>Ну вот я и пытался выяснить, что же именно он хочет сказать.

N>А "чему блокироваться" — повторяю: вызывая написанный не тобой код, ты в общем случае не знаешь, на что он способен и почему.
Угу, но это уже почти к любой реализации относится (экзотику со 100% статической верификацией кода не рассматриваем). По-моему, тут таски и зелёные потоки ничем принципиально отличаться не будут.

S>>Тут путаница Task.Wait() — это не ожидание, это блокировка текущего потока до завершения задачи. Чтобы продолжить выполнение асинхронно, по завершению задачи используется Task.ContinueWith() или await. В этом случае никаких блокировок не будет.


N>await дружит с TPL? При входе в ожидание по await движок TPL способен переключить на другую задачу?

Да, конечно. await по сути — синтаксический сахар к Task.ContinueWith().


S>>Если речь про ожидание завершения внутри самой задачи, то как правило оно перекидывается на само API фреймфорка (которое в итоге работает через всё те же IOCP). Другими словами: если у класса есть async-API, то он не должен его реализовывать через блокирующие операции. Именно по той причине, что ты написал выше.

Угу.

N>Ну опять же НС уже написал — что "внутре" там работа с IOCP через BeginXXX(), которые unmanaged. Что достаточно обидно. Вынести доступ к IOCP на верхний уровень могло дать результат не хуже.

В теории никто не мешает написать свою реализацию шедулера, который будет обрабатывать таски через Get/PostQueuedCompletionStatus(). Зачем оно может понадобиться — не соображу.


S>>Task list + Stacks Window — первое, что упоминается в документации по отладке для тасков. Ну и в студии в режиме отладки Task list — на самом видном месте, не заметить сложно.

N>Ты не понял. Я не про то, что если использовать отладчик, то в нём использовать соответствующие средства. Я про само использование отладчика.
Тут ещё проще — на дотнете почти все программируют под студией (сотые доли процентов любителей emacs и проценты xamarin-а не рассматриваем). Разобраться с нуля с тасками, не запуская отладку, или не читая материалов по сабжу... ну, можно, но это ещё больший хардкор чем емакс наверно

Ну а раз читал/отлаживал, то говорить "Таски нельзя отладить" совсем странно)

N>Соответственно и мой интерес это "а что новенького придумали коллеги из другого лагеря?"

С технической точки зрения — абсолютно ничего, максимум подтюнили пул потоков под частое переключение задач. Но и тут используется классическая work-stealing queue, т.е. ничего нового.

С прикладной точки зрения MS протащили асинхронность почти через всё клиентское API в winrt и добавили в языки синтаксический сахар для максимально простого использования этой асинхронности. Может, я чего и путаю, но в мейнстримных языках я ничего подобного не припомню.


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

N>Но твоё возражение не менее некорректно.
Угу. Там уже сам спор скатывался в никуда — любое долгое объяснение раздёргивалось на цитаты и каждая до бесконечности обсасывалась. Чтоб не спорить — фиг с ним, отладить нельзя, ибо сказано
Re[50]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Sinix  
Дата: 19.05.14 13:54
Оценка:
Здравствуйте, Ikemefula, Вы писали:


I>Собственно меня сильно смущает, что в тасках реализовали всё подряд, а вот простую асинхронную модель как то забыли поддержать.


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

Как полувременное решение, есть готовые third-party-решения, и твой пример можно решить через
using (await lockKey.LockAsync())
{
  var x = await ReadAsync();
  await WriteAsync(Modify(x));
}

, или через compare-exchange loop, типа вот такой.

впрочем, если к ресурсу будет обращаться только код выше (допустим, он обёрнут в while(true)), то LockAsync() не нужен — await гарантирует сохранение порядка выполнения. Как оказывается на практике — подход из цепочки await-ов прекрасно масштабируется: для параллельной обработки запускаем асинхронно специализированный таск, дожидаемся завершения через await, продолжаем цепочку.

А вот с той самой параллельной (конкурентной) обработкой на сегодня конь не валялся.
LockAsync — это слишком низкий уровень для TPL, утонем в взаимоблокировках. Чтобы хоть как-то обмануть Амдалла, нужно или раскидывать данные ко отдельным обработчикам (см PLinq, tpl dataflow, rx), или стараться использовать lock-free где это возможно (см System.Collections.Concurrent).

Удобный синтаксис наподобие await к этому зоопарку пока не прикручен по одной простой причине — пока непонятно, какую из конкурирующих моделей выбрать
Re[54]: Зачем Майкрософту рубить сук, на котором он сидит?
От: alex_public  
Дата: 23.05.14 04:55
Оценка: 2 (1)
Здравствуйте, netch80, Вы писали:

Влезу в разговор — может пояснение по C# от не любителя C# окажется более понятно. )))

Значит async/await — это просто синтаксических сахар компилятора, позволяющий ему формировать сопрограмму (причём всего лишь stackless, а не stackfull) из линейного кода. И это собственно всё. Никакой прямой связи с многопоточностью (низкоуровневыми thread или высокоуровневыми task) или же асинхронным вводом-выводом (кстати польза от этого дела весьма неоднозначна) тут нет. Хотя очевидно, что подобные сопрограммы могут (и собственно только этим и заняты в C#) использоваться для удобной обработки реакции на завершение обработки данных в других потоках или же асинхронной операции ввода-вывода.
Re[54]: Зачем Майкрософту рубить сук, на котором он сидит?
От: Sinix  
Дата: 29.05.14 10:55
Оценка:
Здравствуйте, netch80, Вы писали:

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


Кстати, на DevCon'14 идёт неплохой доклад на тему, "Глубокое погружение в инфраструктуру асинхронного ввода-вывода для веб приложений". Насчёт глубокого они погорячились, но общая картина даётся.

Как запись выложат — добавлю ссылку.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.