thedeemon: (passport)
[personal profile] thedeemon
На языках программирования можно ввести отношение частичного порядка "А < Б", когда с языка А на язык Б пересесть можно, и это ощущается как прогресс, а вот обратно возвращаться очень неохота и мучительно. Если не ошибаюсь, [livejournal.com profile] lionet в свое время заметил, что в этом отношении заметны две вершины - хаскель и лисп, с их высоты все остальные языки кажутся недостаточно хорошими. Но занятно другое: у этого отношения есть также два дна, по сравнению с которыми все другие языки выглядят превосходными, - это PHP и C++. :)

После ряда других языков мне заставить себя писать что-то на С++ очень сложно, но иногда такая необходимость возникает. Помогает сгладить моральные мучения лишь возможность найти в языке крупицы чего-то хорошего. Нынче вот взялся за новую реализацию своего фирменного super resolution движка, и что меня сейчас радует и выручает, это присутствующие в языке элементы зависимых типов. У меня код оперирует блоками разных размеров и векторами разной точности: это могут быть целые координаты в кадре низкого разрешения, в кадре высокого разрешения, а также координаты с полупиксельной и четвертьпиксельной точностью. Плюсовые шаблоны позволили описать эти вещи как семейства типов, индексированные целочисленными значениями, т.е. натурально зависимые типы получились:
#define VP_LOWRES  1
#define VP_HIRES   2
#define VP_HALF    3
#define VP_QUARTER 4

template <int Prec>
class Vec
{
public:
    int x, y;

    Vec(int vx, int vy) : x(vx), y(vy) {}
    Vec operator+(Vec<int Prec> &a) { return Vec<Prec>(x + a.x, y + a.y); }
    Vec operator-(Vec<int Prec> &a) { return Vec<Prec>(x - a.x, y - a.y); }
    Vec<Prec + 1> refine()          { return Vec<Prec + 1>(x*2, y*2); }
    ...
};

class Plane
{
    ...
    template<int W> void readBlockQP(Vec<VP_QUARTER> v, MonoBlock<W> &block);
    ...
}

В результате блоки разного размера - это разные типы, и векторы разной точности - разные типы, реально очень помогает не запутаться. Плюс компилятору подспорье: у многих циклов число итераций теперь известно статически, можно хорошо оптимизировать. В иных языках для таких вещей можно использовать phantom types, но там может быть сложнее сделать функцию refine, переводящую вектор на следующий уровень точности, в соседний слой семейства. Все-таки очень удобно, когда с параметром типа можно делать всякую арифметику и использовать его сразу на двух уровнях: типов и выражений.

К слову о зависимых типах. Одна тривиальная мысль о них мне лично оказалась весьма полезной для понимания. Мы привыкли в функциональных языках обозначать тип функции из А в В как А -> B, где А и В какие-то конкретные типы вроде Bool и Int, элементами которых служат значения вроде true и 2. Теперь добавим в систему типов еще один тип, назовем его Type, элементами которого являются типы. Тогда A -> Type будет просто типом функции, которая каждому элементу А сопоставляет какой-то тип. Это и получится зависимый тип, и именно так он и обозначается в соответствующих языках. Например, пишут B : A -> Type и говорят, что В - это зависимый от А тип, или семейство типов, индексированное значениями из А. Но эту же запись можно воспринимать и буквально - как обычную функцию, просто кодомен у нее не совсем обычный.

Date: 2013-05-09 08:48 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
Я щас попробую объяснить.

int blah = async compute();

В месте вызова compute() кто-то должен сохранить состояние функции-вызыватора. Куда? Без async всё складывается в железный стек. С async всё, что лежит на стеке, должно уйти куда-то ещё - в мягкий стек; мы же хотим, чтобы железный поток не простаивал, а занимался делом.

1. Поскольку мы хотим смешивать async и не async вызовы, стек не может быть железным и в случае не async вызовов. Поток должен вести мягкий стек. Совокупность мягких стеков и есть совокупность зелёных ниток. Речь идёт о том, что якобы зелёные нитки вести дешевле, чем железные? Выгоды преувеличены.


2. Раньше было:

compute();
finish();

Теперь кто-то эти методы может вызвать async. Раньше у вас были головняки со state transition гарантированным program order, теперь у вас головняки с concurrency in practice, где ничего такого не было. Как пример, запись в поток в цикле - речь не обязательно о файловом потоке, подумайте о шифровании или сжатии. Если функция-вызыватор сама не позаботится о гигиене использования потока, вместо нормального цикла получится асинхронный ад с out of order заполнением потока.


3. async Task<blah> oops() { async oops() } - интересно, что переполнится в этом случае и какова диагностика? Тут ещё вариант, что я не понял, как правильно выразить эту мысль - но должно же быть возможно выразить бесконечную рекурсию. Диагностика этого не то, чтобы невозможна, но хотелось бы услышать, как это решается в данном езыке.

Date: 2013-05-09 09:19 am (UTC)
From: [identity profile] soonts.livejournal.com
>куда-то ещё - в мягкий стек
В кучу уходит, не в стек.

>кто-то эти методы может вызвать async
Этот кто-то .NET framework.

>теперь у вас головняки с concurrency in practice
Я написал довольно много async-await кода (начинал году в 2010 с Async CTP). Этот подход как раз решает эти ваши "головняки с concurrency in practice", а не создаёт. Очень часто удаётся написать даже очень нетривиальный параллельный код без единой блокировки, только с использованием гарантий, которые дают компилятор и рантайм.

>Если функция-вызыватор сама не позаботится о гигиене использования потока
Ничего не нужно делать.

>вместо нормального цикла получится асинхронный ад с out of order заполнением потока.
Не получится, за вас framework это сделает. Framework гарантирует, что statements в async методе будут исполняться строго последовательно, хотя и не обязательно одним потоком конечно.
А может быть и одним потоком, поведение зависит от того есть ли synchronization context, framework сам умеет при необходимости маршалить вызовы в тот поток, из которого вызвали await.

>async Task oops() { async oops() }
Имеете в виду видимо
async Task oops() { await oops(); }?
Получите обычный stack overflow exception в рантайме.

>должно же быть возможно выразить бесконечную рекурсию
Элементарно.
async Task oops() { await Task.Yield(); await oops(); } - ничего не будет падать, и получите бесконечную рекурсию, только зачем?
Edited Date: 2013-05-09 09:25 am (UTC)

Date: 2013-05-09 12:12 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
"В кучу уходит, не в стек."

Вот это и есть мягкий стек. В кучу должны складывать все - и вызыватор, и вызываемый.


"Этот подход как раз решает эти ваши "головняки с concurrency in practice", а не создаёт."

я думаю, моя мысль ускользнула. Пример на гугле показывает как раз головняки с concurrency in practice, а ваш пример с await s.readByte() ничего интересного не делает. Интересное как раз вот в чём (по памяти пример из гугла):

Task<string> getString = connection.getStringAsync("http://..."); -- здесь, я так понимаю, не только async таск создаётся, но и запускается. это fork
DoSomethingIndependent(); -- Вот это вот и есть concurrency in practice
return await getString; -- это join

Вызыватор должен быть в курсе, где там внутри методов есть async, иначе getStringAsync и DoSomethingIndependent напорются на конкурентность, где её не было. Проблемы нет, если SomethingIndependent действительно independent.


"Получите обычный stack overflow exception в рантайме."

Прекрасно. А теперь внутри getStringAsync вызывается await функции, вызывающей кого-то ещё и т.д. Как диагностировать, что вызвало stack overflow exception?

Date: 2013-05-09 12:33 pm (UTC)
From: [identity profile] soonts.livejournal.com
>В кучу должны складывать все - и вызыватор, и вызываемый.
Нет, вызываемый тупо возвращает экземпляр Task. Он может быть даже выполнен синхронно уже. Таск вовсе не обязан быть реализован через async метод, он может работать как угодно ещё.
Сам async метод - да, складывает в кучу конечно, но это автомагически делает для вас компилятор.

>создаётся, но и запускается. это fork
Нет никаких форков. Все Winapi давным-давно асинхронные, в WinRT вон вообще убрали API блокирующий IO, шоб неповадно было.

>Вызыватор должен быть в курсе, где там внутри методов есть async
Тот кто зовёт вообще не в курсе как именно реализовано API. Для него это самый обычный метод, возвращающий объект Task.

>иначе getStringAsync и DoSomethingIndependent напорются на конкурентность, где её не было
Ещё раз повторяю. Фреймворк by design гарантирует исполнение вашего async-метода не более чем одним потоком одновременно (если вы конечно его только один раз позвали).

>Как диагностировать, что вызвало stack overflow exception?
Точно так же как до появления асинков, посмотреть в дебаггере на call stack.

Date: 2013-05-09 06:57 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
Если всё последовательно, в каком порядке выполнятся getStringAsync и DoSomethingIndependent? getStringAsync, я так понимаю, выполняется до точки, где ему придётся заблокироваться. Далее возвращается в точку, где вызывается DoSomethingIndependent. DoSomethingIndependent чё-то сделал, и теперь мы "довыполняем" getStringAsync - ждём данные, потом что-то ещё делаем с этими данными на выходе из getStringAsync. Так? Вот в этой последовательности и есть проблема. Если getStringAsync на входе записал в файл три байта, потом DoSomethingIndependent на самом деле файл вытер, а getStringAsync хочет на выходе ещё что-то в файл написать - асинхронный ад. Это не возможный вариант развития событий?


И вон в параллельной ветке упоминают pool, который async таски подбирает.


Про fork - это термин.

Date: 2013-05-09 09:33 pm (UTC)
From: [identity profile] soonts.livejournal.com
await это не fork.

>в каком порядке выполнятся getStringAsync и DoSomethingIndependent?
Если в каком-то методе написано
await getStringAsync();
doSomethingIndependent();
то doSomethingIndependent() будет исполнено только после того как getStringAsync() вернуло результат, и вообще не будет исполнено если getStringAsync() упадёт с исключением.

>в параллельной ветке упоминают pool, который async таски подбирает.
Эти и другие детали раализации вовсе не обязательно знать, шоб писать корректный код.

Date: 2013-05-09 09:54 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
в примере речь об async как форк.

вот: http://code.msdn.microsoft.com/Async-Sample-Example-from-9b9f505c/sourcecode?fileId=63457&pathId=166422599

(если не проглотит как спам)

GetStringAsync написан так же, как и AccessTheWebAsync, т.е. на входе что-то делается, потом await сокета, потом на выходе что-то делается с результатом. В момент await внутри GetStringAsync мы возвращаемся на строку, где GetStringAsync вызван. DoSomethingIndependent не зависит от строки, но нас должно волновать взаимодействие его с остальным кодом в GetStringAsync.

Если мой вопрос всё ещё не понятен, то бог с ним.

Date: 2013-05-09 10:11 pm (UTC)
From: [identity profile] soonts.livejournal.com
Посмотрел на пример.
Да, в нём асинхронная порция функциональности GetStringAsync вполне может работать параллельно с исполнением DoIndependentWork().

Это вам не подходит? Хотите сначала закончить GetStringAsync потом DoIndependentWork?
Замените "Task<string> getStringTask =" на "string urlContents = await ".

Date: 2013-05-10 07:24 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
не то, чтобы не подходит, а то, что нужен контроль над тем, где можно делать асинк, а где - нет. И GetStringAsync имеет порцию кода ещё и после асинхронной порции функциональности - которая тоже имеет шанс "испортиться". Это суть моего вопроса об асинхронном аде.

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

Date: 2013-05-10 02:30 pm (UTC)
From: [identity profile] soonts.livejournal.com
>GetStringAsync имеет порцию кода ещё и после асинхронной порции функциональности
Не имеет. GetStringAsync как и подавляющее большинство async методов имеет всего 2 порции кода: синхронная первая, и callback когда получен результат.

>могут ли суб-классы переопределить поведение таким образом, что оно неожиданно станет асинхронным адом
Если убрать слово "неожиданно" — ответ "конечно могут".
Например достаточно добавить в последнюю строчку реализации GetStringAsync "Task.Run( шонибудь )", шонибудь запустиццо и продолжит работать параллельно даже после того как GetStringAsync вернёт результат.
Неожиданно такого не происходит.

Profile

thedeemon: (Default)
Dmitry Popov

February 2026

S M T W T F S
12 34567
891011121314
15161718192021
22232425262728

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Mar. 16th, 2026 07:59 am
Powered by Dreamwidth Studios