Дмитpий Hecтepук

Блог о программировании — C#, F#, C++, архитектура, и многое другое

Мысли о выборе технологий

leave a comment »

Мне кажется, что идеальной состояние любой технологически ориентированной компании — это диверсификация. Иначе просто рискуешь нарваться на то что ты внезапно нерелевантен. Хороший пример – это разработчики Windows Mobile (это то, что предшествовало Windows Phone) — их по сути всех «кинули», и все их скилы (там .NET Compact Framework и иже с ним) внезапно стали бесполезны.

Я тут думал на тему того, на базе чего имеет смысл сегодня начинать писать новый продукт. Идеи у меня конечно все направлены в сторону «веба», т.к. и ежу понятно, что идеальная стратегия для конечного пользователя – это «облако», даже если это облако приватное, т.е. существует в пределах инфраструктуры компании, а не отдано «на аутсорс» всяким Amazon, Microsoft и так далее.

Так вот, все равно, в большинстве случае, на стороне сервера я прихожу к тому, что единственный возможный вариант для создания back-end’а чего бы то ни было это С++, и что все Java/.NET решения особого смысла для высокотехнологичных решений (а не решений в стиле «база для веб-мордочки») не имеют. Агрументация моя примерно такая:

  • Мы просто не можем позволить себе ограничивать себя в плане операционной системы. А это значит что можно использовать только то, что работает везде. Следовательно, сразу «отпадает» .NET т.к. при всей возможной опенсорсности Roslyn, использовать Mono я пока как-то не хочу.

  • Мы не можем себе позволить использовать новые, непроверенные, экспериментальные языки. Мне нравится язык D, например, но ему нужено еще очень много времени чтобы библиотеки во-первых починить где они не работают а во-вторых чтобы появились те либы, которых не хватает; ну и конечно вопрос с инструментарием и поддержкой тоже не решен.

  • Единственный способ воспользоваться интересным железом, которое на сервере совсем не зазорно размещать, это C/C++. Хотя по правде сказать, у Java есть поползновения сделать поддержку GPU на базе Aparapi (этот фреймворк в свое время поставляла ATI для использования OpenCL из Java), но GPU – это ведь только одна, причем не самая гибкая, технология ускорения вычислений.

Можно, конечно, использовать целый набор языков программирования — все равно в вебе придется использовать JS, например — но мне кажется что если использовать С++ как основу, то уже распыляться не имеет особого смысла. .NET и Java являются языками производительности (productivity languages), но мне кажется что они скорее являются языками лени. Конечно, разработка на С++ на первом этапе медленнее, зато преимущества, мне кажется, очень даже существенны:

  • Инструментарий (профиляторы, анализаторы) намного более зрелый

  • Библиотеки тоже намного более зрелые

  • Возможность оптимизации для конкретной архитектуры. (Надеяться на то векторизацию от какого-нть JIT компилятора – наивно.)

  • Намного более вменяемые подходы к параллелизации, как на декларативном (OpenMP) так и императивном уровне.

  • И да, тот факт что специфичные устройства (в моем случае – Tesla, Xeon Phi и FPGA) — всем могут быть запрограммированы на С++. Хотя в последнем случае все сложнее, т.к. OpenCL, который доступен для этих целей, мне пока не импонирует. Но рано или поздно придется и на него посмотреть.

Нравится вам это или нет, но типичные процессы рано или поздно все-таки перетекут в «удаленные» структуры, которые будут выделять на наши тривиальные задачи намного бо́льшие ресурсы чем те, которые может предоставить ваш ноутбук или рабочая станция. А соответственно, все эти кластеры тоже нужно программировать так, чтобы взаимодействие между машинами было наиболее прозрачным. И для этих целей у нас есть такие вещи как MPI, которые позволят строить гетерогенные сети как из машин, так и из устройств вроде Xeon Phi.

Конечно все это идиллия, и C#/Java программисты будут ругаться и плеваться от одного вида ручного управления памятью. Но зато если вы с самого начала работаете с ручным выделением/уничтожением, умными указателями и соответствующими паттернами использования, то вас уже ничего не должно пугать.

Written by Dmitri

18 Апрель 2014 at 23:40

Опубликовано в Technology

Мысли об облачных IDE

Мне кажется что никто на сегодняшний день не понимает, что такое облачные IDE. Если посмотреть на то, что люди реально пишут, получается у них следующее

  • Редактор в облаке который пусть и имеет подсветку синтаксиса, но остается достаточно рудиментарным и не обладает даже сотой долей набора фич, которые нужны современным пользователям всяких IDE.

  • Возможность сразу использовать 100500 языков, т.к. все компиляторы уже установлены и настроены. (Только в большинстве случаев разработчик использует один язык, но выбор – это все равно хорошо.)

  • Возможность компилировать и запускать программы в облаке соответствующий код в sandbox-е, так чтобы нельзя было снести к чертям всю систему. EdX даже умудрился сделать подобную штуку для CUDA, что не тривиально.

  • Коллаборативные возможности вроди sharing (a la GitHub Gist), ветвления (как в IdeOne) и прочие возможности, которые являются фактически source control-ом.

Социальная составляющая кодинга, как показывает GitHub – это очень хорошо. Но не хватает понимания того, что на практике нужны еще и

  • Работающий отладчик, который непонятно как делать в облаке т.к. это существенно понижает возможность разделения ресурсов: пока у меня стоит задетый breakpoint никто не имеет права трогать эту машину.

  • Поддержка тестирования — хочется иметь возможность запускать вагон тестов и фактически использовать серверную часть не только для continuous integration (это слишком просто), но именно для continuous testing в стиле NCrunch и подобных

  • Поддержка профилирования — это проще чем отладка и в принципе должно быть как-то реализуемо.

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

  • Поддержка мониторинга — раз уж на то пошло, почему бы не мониторить приложения и выдавать статистику по ним? Кстати, именно это уже делается в VS Monaco/Azure.

Основная проблема с Cloud IDE – это то что все забыли зачем облако. То есть облака рассматриваются как «социальный дропбокс», хотя на самом деле, облако – это место где хорошо масштабируются ресурсы. Зачем нужны ресурсы?

  • Во-первых, на (общих) серверах можно ставить более серьезное железо. Если учесть что разработчики для всех целей кроме CI не используют все мощности компа постоянно, покупка 32-процессорных систем с FusionIO и прочими вещами уже не кажется такой страшной.

  • Многие вещи параллелизуются. Например компиляция. Я уже какой год использую IncrediBuild вместо встроенного MSBuild. В облаке можно отмасштабировать компиляцию пропорционально размеру проекта.

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

  • Тестовое покрытие (code coverage) – тоже штука дорогая и в принципе на него можно выделать машины побыстрее.

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

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

  • Перекрестный анализ проектов на предмет использования того или иного API. Мне почему-то кажется, что modus operandi взаимодействия с типовой библиотекой «шаблонизируется», и тем самым может быть использован для предоставляния пользователю более умных подсказок.

  • Можно использовать специализированное железо (например Xeon Phi) – возможность, которая недоступна большинству разработчиков ввиду дороговизны а также невозможности втиснуть такую карту в типичный MBP. (В теории, можно строить специализированное железо для ускорения задач программирования, но боюсь индустрия на такое не способна – не хватает кругозора и понимания вещей.)

Я надеюсь что рано или поздно можно будет использовать эти и другие возможности в облаках и на кластерах тоже. Когда – понятия не имею. Но мысли пока вот такие. А как еще можно использовать облако для разработки? Напишите в комментариях. ■

Written by Dmitri

10 Март 2014 at 23:23

Опубликовано в Development

Отмечено как , , , ,

Мысли о «новом» обещанном нам C#

Как вы понимаете, Microsoft сейчас толком не до языковых фич т.к. сам компилятор переделали и даже уже сами переключились на Roslyn в процессе разработки, что как бы намекает на большие планы на 2014. Соответственно, те фичи которые Мадс осветил на NDC London не являются системообразующими, поэтому получите список:

  • Первичный конструктор в стиле class Size(int cx, int cy) { ... }, где как вы догадались, cx и cy это значения которые потом можно присвоить пропертям. Позволяет писать new Size(320,240), и вообще своровано с F#.
  • Возможность задавать значения readonly свойствам в стиле public int Width { get; } = cx. Даже не знаю что тут сказать – у меня часто свойства еще и в UI используются, а там все равно будет поле, так что нет разницы. (Ждите от Roslyn автореализации INPC? Не дождетесь.)
  • Property expressions, т.е. возможность писать property get без собственно блока get{}, return и прочих излишеств:
    public int Area => Width * Height;
    
  • Та же тема для методов. Фактически, просто удаление скобочек и return, но я уже вижу как новички пихают в эти стейтменты всякий мусор:
    public Point Move(int dx, int dy) => new Point(X + dx, Y + dy);
    
  • params IEnumerable, потому что когда хочется неограниченное число аргументов, то IEnumerable удобнее чем массив? На самом деле, могли как в D попробовать делать вещи через convention over configuration, т.е. вместо ключевого слова params просто сказать, что если есть только один параметр и он IEnumerable<T> или T[], то давать пользователям передавать массив через Foo(a,b,c) – и все были бы довольны.
  • Monadic null checking означает что вместо person.With(x => x.Address).With(x => x.HouseName) можно написать person?.Address?.HouseName, и везде будут проверки на null. Шикарно конечно, может немного поздновато, к тому же как показала практика, проверка на null – это не единственный concern, которых можно вкладывать в цепочки из лямбд.
  • Вывод типов для конструкторов, т.е. вместо Tuple.Create(foo,bar) можно наконец-то писать new Tuple(foo,bar)
  • Инлайновые декларации для out параметров по мне так самый большой фейл, пожалуй. Идея в том чтобы можно было писать
    if (foo.TryGetValue(out int x))
    {
      // use x, it's already been declared!
    }
    

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

    let (x1,x2) = SolveQuadratic(1,10,16);
    

    И не важно, получили ли мы кортеж из (x1,x2) или просто две переменных – главное что результат метода записывается в том месте, в котором вы ожидаете, а не в аргументе, где декларации переменной – явно не место.

  • Импорт типа в пространство имен: уже хорошо, сделаем using System.Math чтобы не пользоваться поистине невменяемыми с точки зрения здравого смысла конструкциями вроде Math.Sin(). Хотя это только полумера: еще нужно удалить заглавные буквы (ну зачем они?), добавить оператор возведения в степень (с целочисленной перегрузкой), и уже будет можно дышать. Но разве ж кто-то об этом думает?

Ничего из списка выше особо не впечатляет. Гораздо интересней сам Roslyn (все же надеюсь что он будет «готов» к VS2014), ну и как это не странно, С++ сейчас развивается как-то быстрее, причем без всякого переписывания компилятора. ■

Written by Dmitri

21 Декабрь 2013 at 13:36

Опубликовано в C#

Коротко про Xeon Phi

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

  • SIMD сложно использовать потому, что нужно конвертировать обычные вычисления в абсолютно другой синтаксис, где, во-первых, другие типы данных (__m128i и подобные), работают далеко не все операции (например, в SIMD может не оказаться деления) и используются они не через обычные операторы: например вместо a*b нужно писать _mm_mul_ps(a,b). Понятно что мало кто будет напрягаться с этим, для этого даже отдельное расширение языка есть. Нет, я не шучу, пожалуйста: Intel Extensions for SIMD. Вы только вдумайтесь – отедльное расширение языка чтобы пользоваться чем-то, что присутствовало в процессорах со времен MMX! Я уже не буду говорить про то насколько все это эволюционирует и что писать для этого портативный код фактически невозможно.

  • CUDA это еще один гвоздь. Казалось бы, что плохого, модель более менее понятна. Но! Оно во-первых не особо портируется между девайсами (сам PTX портируется, но речь не об этом), т.к. девайсы все разные и никакого… свопа на диск и прочего нет, у всех свои параметры, писать обобщенный код нудно. Ну и потом, на CUDA нельзя гнать ничего кроме параллельных вычислений. Параллельных. Вы не можете делать 1024 разные вещи на CUDA. Только одинаковые, или с небольшими вариациями. Для обработки картинок? Супер. Для анализа кода? Да вы издеваетесь… (В эту же корзину идут OpenCL, которым никто не пользуется, и AMP, которым тоже никто не пользуется.)

  • FPGA разработка это вообще рецепт «как сделать классные технологии недоступными для простых смертных». То есть в отличии от SIMD, который у вас поддерживается в проце, и CUDA, которая в любой карте NVIDIA поддерживается в каком-то (порой, правда, весьма унылом) состоянии, FPGAшки остались чем-то на уровне шаманства. И языки другие (VHDL это нечто), и парадигма другая, и самое главное что разработчики не понимают нафиг это надо если не купить COTS продукт и не засунуть его быстренько в комп. На самом деле купить, и засунуть, просто это чертовски дорого, а все хотят выгоду здесь и сейчас. Не получится, товарищи.

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

И казалось бы, идея «купи компьютер и вставь в компьютер» фактически умерла, если только у вас не одна математика (тогда вы в шоколаде). Что же делать? Но недавно мне попалась новая партия добра (24шт.) под названием Xeon Phi. Думаете «еще одна проприетарщина»? А вот нет. Phiшка – это 60 процов которые – важно –

поддерживают x86

Может кто-то из вас не вкурил насколько это круто. Ладно. Попробую объяснить.

В ваш комп, условно говоря, воткнут еще один комп на PCI шине. Этот комп может работать в двух режимах: либо вы его просто используете как, эмм, отдельный комп – у него там Linux на борту, с ним вполне можно общаться; либо же вы отгружаете часть ваших задач на него.

Intel Xeon Phi Card

Вообщем шикарно, да? Вы фактически можете поставлять решения (т.е. полнофункиональные решения software+hardware) на таком девайсе. Но стоп, не все так просто. На самом деле, было бы даже странно, если бы наша лодка счастья не разбилась бы о быт в той или иной форме.

Итак, как вы уже догадались, для использования Фишки нужно

  • Отдельный компилятор

  • Тулзы для коммуникации с Фишкой

  • Магия в коде :(

То, что нужно как-то общаться с девайсом это понятно. Отдельный компилятор понять тоже можно: это конечно продукция Intel, но продукция эта использует другие процы, соответственно внутренняя реализация может как-то отливаться… хотя стоп, это же x86.

На самом же деле, поддержка Intel MIC (MIC = many integrated cores, то есть «очень много ядер к нам пришло») с точки зрения кода выглядит очень похоже на CUDA. Серьезно, посмотрите:

  • Для того чтобы что-то отгрузить на Фишку, можно либо использовать __declspec(target(mic)) int* stuff либо же сделать pragma push/pop.

  • Есть два режима как передавать данные – data sharing и data marshaling. В CUDA тоже вариантов всяких аллокаций и sharing’а вагон.

  • Модель отгрузки поддерживает разные парадигмы, например OpenMP. В этом случае просто пишем #omp parallel for и так далее.

  • В прагме для отгрузки идут отметки о том что отсылается и что получается (in/out/inout), прямо в лучших традициях COM. Я правда не уверен что читатели моего блога знают что такое COM :)

  • В случае с отгрузкой, вызов отгруженной функции выглядит как-то брутально: _Cilk_offload_to (target_id) foo().

  • Если данные шарятся, их нужно по-особенному создавать и удалять: как вам _Offload_shared_align_free(vals).

Вообще смотря на это мне как-то не особенно страшно. Ну да, чуточку прагм и немного головной боли в плане определения того, что дескать я именно вот этот массив хочу получить как результат, и вот тебе его длина. Да, это кстати намек на то что variable-size структуры могут как-то не сростись.

Пока у меня про Фишки все. Признаюсь, что с ними интересней играть чем писать про них. Но что я хочу чтобы вы поняли так это то, что это революция. Только пришла она поздно: сейчас у нас «облака» и уже локально что-то тяжелое гнать не так важно. К тому же, непонятен выигрыш для не-math задач. Нужно поизучать.

И да, судя по тому что пишут, для numerics эта штука послабее CUDA будет. Но это не важно!

Written by Dmitri

1 Декабрь 2013 at 19:10

Опубликовано в Hardware

Отмечено как , ,

Что нового в С++ под Visual Studio 2013

В прошлом посте речь шла про нововведения в F# 3.1, в этом же мы немного поговорим про то, что нового в поддержке С++ в Visual Studio 2013. Заметьте что нам обещали что обновления компилятора С++ будут out of band, но как-то этого не произошло — а может просто совпало с релизом студии. Так или иначе, вот очень прозаичный список того, что появилось:

  • Поддержка однородной инициализации объектов с использованием фигурных скобок. Вот несколько примеров:
    int meaningOfLife {42};
    int meaningOfLife2 = {42}; // можно и с =
     
    Address a(100, "London road"); // обычный конструктор
    Address a2 { 100, "London road" }; // то же самое через инициализатор
     
    vector<int> v { 0, 1, 2 }; // ну наконец-то!
    

    К вопросу о том как это делается: вот эта штука { a, b, c } называется initializer list ну или std::initializer_list если точнее. Соответственно если вы хотите инициализировать имя (+ возможно фамилию и отчество), нужно писать что-то вроде:

    class Name
    {
      string firstName, lastName;
      vector<string> otherNames;
    public:
      Name(initializer_list<string> stuff)
      { 
        // у initializer_list нет оператора []
        auto b = begin(stuff), e = end(stuff);
        firstName = *b;
        if (++b != e)
          lastName = *b;
        while (++b != e)
          otherNames.push_back(*b);
      }
    }
    

    Вот как-то так. Интересный вопрос в том как передать произвольный набор данных. В .NET мы пишем params object[], а тут с этим сложнее, хотя… читайте дальше.

  • Введен аналог make_shared для единичных инстансов (т.е. unique_ptr) под названием make_unique. Напоминаю что предпочтительно как раз использовать эти методы нежели писать например shared_ptr foo(new Bar). Но вообще это конечно читинг – тащить фичи из Буста :)
  • Строковые литералы в которых не производится замена escape sequences, т.е. raw string literals. В .NET мы используем префикс @, а тут это префикс R:

    string s = R"c:\windows"; // backslash!
    string s2 = R"Hello
    world!"; // line breaks!
    
  • В предыдущих версиях С++, дефолтные параметры шаблонных аргументов могли фигурировать только в параметрах типа. Сейчас это можно делать и в функциях.
    template <typename T, typename U = less<T>> T FindSomething(vector<T>& v, U u = U())
    
  • Кортежи! Опять же, из Буста. Определается как tuple<int,string,Foo>, использует списки инициализации (см. выше) или же специальную конструкцию make_tuple() (эти две вещи эквивалентны), достается адским (по сравнению с .NET) способом через get<2>(myTuple) что я считаю просто кошмарно хотя наверняка появилось из-за того, что в отличии от C# у нас есть вариадичность.
  • Вариадические шаблоны — ухх, фича чтобы сломать кому-нибудь мозг. Вариадические шаблоны позволяют иметь неопределенное количество шаблонных аргументов у типа. Намек: а как кортеж работает? Ведь у него может быть сколько угодно аргументов с разными типами, так?

    Вообщем, это С++11я фича языка, которая как раз дает возможность использовать такие вещи как make_shared/make_unique. Точнее вещи такие раньше просто использовали вагон перегрузок (до N=5), а теперь мы можем вздохнуть т.к. теперь можно иметь 25 параметров, если действительно нужно.

    Работает эта штука и для функций и для классов, и выглядит прозаично:

    template <typename... Ts> class Stuff {};
    templat <typename... Ts> void func(Ts... values) {}
    

    Также у нас есть оператор расширения этих «неопределенных» параметров ..., что используется например для форвардинга (т.е. return new Foo(forward<Ts>(args)...)). Этот хитрый оператор собственно просит компилятор развернуть параметры как список (например a, b, c). Соответственно, есть 2 варианта:

    • Отослать параметры кому-то, возможно в функцию которая принимает фиксированное кол-во аргументов.
    • Замутить рекурсию. Например если у вас есть T... то можно сделать функцию foo(T a, T b, T... rest) и просто рекурсивно вызывать эту функцию с (b,rest) или просто (rest), тем самым отсекая первые 1/2 элемента.

Ну вот как-то так. ■

Written by Dmitri

26 Октябрь 2013 at 14:14

Опубликовано в С++

Что нового в F# 3.1

Недавно в пререлиз VS2013 была залита версия F# 3.1 в которой, как ни странно, есть несколько полезняшек которые немного упрощают жизнь. В частности:

  • Наконец-то кейсы дискриминированных объединений можно именовать. То есть если мы раньше писали
    type Stuff =
      | Pair of float * float
    

    то теперь можно дать имена аргументам, т.е.:

    type Stuff =
      | Pair of first : float * second : float
    

    Не могу сказать что это выглядит красиво, но это мне реально нужно для MathSharp, где разные конструкции MathML как раз и представлены таким большим объединением, и хочется помнить какой тип к чему относится. Да, и конечно же теперь есть паттерн-матчинг на все это, т.е. можно писать

    match myPair with
    | Pair(fisrt = 0.0f) -> ⋮
    
  • F# явно пытается стать чем-то вроде MATLAB-а в плане работы с матрицами – в 3.0 они сделали так чтобы матрицы можно выпиливать кусочки например так foo.[2..5,*] что в MATLAB выглядело бы как foo(2:5,:), а в 3.1 можно еще и брать отдельные элементы, например
  • foo.[2,*] // 2й ряд
    bar.[*,5] // 5я колонка
    
  • Вывод типов для LINQ. Давно пора. Пока вы в уютном сишарпике писали foo.Where(x => x.Bar), в F# творилось плохое приходилось навешшивать аннотации типов на параметр. Кажись пофиксили, теперь можно просто писать foo.Where(fun x -> x.Bar). Э-эх, если бы они еще fun убрали для лаконичности, хотя это наверное не реально.
  • Раньше, если вы импортировали из C# методы расширения с дженериками (например монаду Maybe — хотя зачем?) то F# их не видел. Теперь в 3.1 все должно работать и, более того, можно самому делать подобные расширения. Правда F# как всегда отличился и добавил анти-ООПшного изврата в сей процесс путем не давания возможности ограничить generic-параметр (в стиле where foo : Bar, new(). Обещают пофиксить в будущем, а пока рекомендуют пользоваться [<Extension>].
  • В констрантных выражениях теперь можно писать вещи вроде
    [<Literal>]
    let foo = "bar" + "baz"
    

Это что касается языка. Помимо этого, есть какие-то попытки улучшить поддержку VS, но если честно — улучшение тултипов и выравнивания кода не даст мне возможность лучше идентифицировать ошибки или делать рефакторинги. Более того, все мои дискуссии с F# community сводятся к тому что все с пеной у рта бегут доказывать что «ReSharper не нужен»,ну и следовательно продолжать диалог сложно.

Что ж, скажем спасибо что язык хотя бы не стоит на месте. Хотя хотелось бы конечно большего. Вот ссылка на пост где помимо языковой части еще затронут инструментарий и либы – может будет интересно. ■

Written by Dmitri

27 Сентябрь 2013 at 11:33

Опубликовано в f#

Отмечено как ,

Немутабельные коллекции

Если переключить NuGet в пререлизный режим, один из пакетов который вы найдете называется Microsoft Immutable Collections. Немутабельность магически приходит в .NET в основном под влиянием F#: тут, относительная неизменяемость встроена в язык, хотя по-хорошему, что касается коллекций, у F# те же проблемы что и у C# т.к. массивы мутабельны: x.[0] = 123; вполне валидное выражение, и let mutable для этого писать необязательно.

Что такое немутабельная коллекция?

Если коротко, то

  • Немутабельную коллекцию нельзя менять

  • Но методы изменения все-таки есть!

  • И каждый метод создает и возвращает новый инстанс коллекции

Естественный вопрос зачем? Классическая реплика насчет потокобезопасности как бы не работает, т.к. есть System.Collections.Concurrent или как там. Но с другой стороны, существуют вполне реалистичные ситуации, в которых нужно и обновить коллекцию и тем не менее оставить предыдущий вариант. Хотите пример? Пожалуйста!

Надуманный пример

Есть такая штука – брокер событий. Это фактически специализированный service bus для приложения, с тем намеком что все события системы кладутся на некую шину через Publish() и потом снимаются с нее с помощью Subscribe(). Соответственно класть в него событие – кошерно, а вот комманду (привет CQRS) – это уже на любителя.

Так вот, у брокера событий есть набор подписок:

private readonly List<Subscription> subscribers = new List<Subscription>();

Есть метод для подписок

public IDisposable Subscribe(IObserver<EventArgs> subscriber)
{
  Subscription sub = new Subscription(this, subscriber);
  if (!subscribers.ToArray().Any(s => s.Subscriber == subscriber))
  {
    subscribers.Add(sub);
  }
  return sub;
}

и для рассылки событий:

public void Publish<T>(T args) where T : EventArgs
{
  foreach (var s in subscribers.ToArray())
    s.Subscriber.OnNext(args);
}

Заметили использование ToArray()? С чего бы это все материализовывать? А с того что подписки и отписки фактически могут меняться в процессе, и материализация — единственный процесс, который спасает нас от этого. Другая альтернатива — как раз немутабельные коллекции. Cам список можно опеределить вот так:

private ImmutableHashSet<Subscription> subscriptions = ImmutableHashSet.Create<Subscription>();

Уже нет readonly т.к. вся суть этой переменной в том, чтобы ее перезаписывать — напомню что .NET гарантирует атомарность этой операции. Соответственно, метод подписки выглядит вот так:

public IDisposable Subscribe(IObserver<EventArgs> subscriber)
{
  var s = new Subscription(this, subscriber);
  subscriptions = subscriptions.Add(s);
}

А соответственно метод рассылки вот так:

public void Publish<T>(T args) where T : EventArgs
{
  foreach (var s in subscriptions)
    s.Subscriber.OnNext(args);
}

Это тоже валидно. Напомню что переменная считывается только первый раз, а далее, даже если ее перезаписали, это не важно — ведь это уже совсем другая переменная.

Что есть в коробке?

Доступен полный набор коллекций – array, list, stack, dictionary, sorted dictionary, queue, hashset, sorted set.

Также доступен ImmutableInterlocked, содержит методы для атомарного присваивания, замены, вообщем все как Interlocked для немутабельных коллекций. В принципе, в примере выше я должен был пометить поле как volatile и воспользоваться этим классом для полного счастья.

Вообще, у меня не так много юз-кейсов всего этого. Если они есть у вас, напишите в комментариях.

Written by Dmitri

20 Июль 2013 at 23:48

Опубликовано в .NET

Отмечено как

Отслеживать

Get every new post delivered to your Inbox.

Join 100 other followers