суббота, 13 сентября 2014 г.

[management] Генри Минцберг об организационных схемах

Цитата из книги Г.Минцберга "Действуй эффективно! Лучшая практика менеджмента" (выделение жирным самого Минцберга -- очень советую обратить на это внимание):

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

[management] Генри Минцберг о разработке стратегии

Фрагмент из книги Г.Минцберга "Действуй эффективно! Лучшая практика менеджмента":

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

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

В свою бытность менеджером довелось ознакомиться с двумя стратегиями, разработанными "за закрытыми дверями" двумя разными группами ТОП менеджеров, а затем спущенными вниз для "ознакомления и разработки способов реализации". К сожалению, оба раза мне не хватило смелости прямо сказать авторам этих стратегий: "Ребята -- это полная х*йня!" Хотя время потом доказало, что так оно и было. А вот эта цитата из Минцберга поясняет почему.

[work.thoughts] Достаточно ли оплаты времени только деньгами?

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

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

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

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

Надежда.

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

Соответственно, работодатель должен платить за время работника не только деньгами, но и чем-то, что питает надежды работника.

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

Но если руководство не хочет или не может обеспечить это "что-то", если по факту все сводится к примитивной форме "мы платим -- вы работаете", а единственный способ приковать к станку удержать сотрудника -- это повысить ему ЗП, то избежать ухода стремящихся что-то сделать людей не получится.

Ну а поиск ответа на вопрос "А способны ли сделать что-то стоящее те, кто ни к чему не стремиться?" можно оставить читателю в качестве самостоятельного упражнения.

пятница, 12 сентября 2014 г.

[life.photo] Нафига фотолюбителю 24Mpx?

Пост навеян ожиданиями и, в конце-концов, свершившимся анонсом Nikon D750, новой полнокадровой цифрозеркалки с 24-мегапиксельным сенсором. Ожиданиями, в том числе и под впечатлениями от анонса Zeiss Otus 1.4/85mm.

С момента начала моего серьезного увлечения цифровой фотографией прошло три года, за это время было отснято тысяч пятнадцать-двадцать кадров, вбухано немеренное количество денег в камеры, в оптику и дополнительные аксессуары, напечатано несколько фотографий более-менее большого размера (включая 90x135см и 45x180см), ну и найден жанр, в котором мне интересно фотографировать. И все это только с 12Mpx камерами.

Так вот, мне очень жаль, что для такого фотолюбителя как я, у Nikon-а есть всего одна современная камера, которая для меня выглядит достойной заменой старичку D700. Это хипстерский Nikon Df. Насколько он удобен в использовании -- большой вопрос. Еще больший вопрос: а стоит ли он своих денег (у нас в РБ он будет стоить чуть больше $3000).

Все остальное у Nikon-а, за исключением профессиональных репортажных камер D4/D4s, уже 24-мегапиксельное. Даже любительская кропнутая зеркалка начального уровня, D3300 -- и та 24Mpx.

Пичаль, пичаль... :(

Не, ну реально не понимаю, зачем фотолюбителю что-то больше 12-16Mpx.

Возможность печати фотографий огромного размера? Ну вот с 12Mpx карточки 40x60см выглядят замечательно. Только сколько их доведется напечатать и где их развешивать? Мы же про фотолюбителей говорим, большинство из которых размещает фоточки 1024px по длинной стороне в соцсетях. Или печатают 10x15см для семейного альбома. Для таких вещей и 6Mpx за глаза хватит.

Зато "весят" 24Mpx в два раза больше, чем 12Mpx. Во что это выливается на практике? Ну вот с последней съемки я привез порядка 1500 снимков. Это составило около 30Gb или две почти заполненных 16Gb карточки памяти. Будь у меня не 12Mpx, а 24Mpx, это было бы уже четыре карточки памяти. И в два раза больше места на винчестере.

Соответственно, компьютер для обработки 24Mpx фотографий должен быть раза в два помощнее. Да и работы по retouching-у должно прибавиться, особенно, если кто-то снимает портреты крупным планом.

Оптика, опять же. Скажем, классный портретник Zeiss Planar 1.4/85mm, отлично работающий на пленке и на 12-16Mpx камерах, оказывает уже не так хорош, когда мегапикселей больше 20. А чем его заменять? При всем уважении к Кену Роквеллу, утверждающему, что никоновский ультразум 18-200 со светосилой 3.5-5.6 или еще более ультразум 18-300/3.5-5.6 -- это отличное "стекло", терзают меня смутные сомнения :)

С фокусировкой на многопиксельной камере, как говорят, несколько сложнее. Не прощают многопиксельные камеры небрежностей в технике съемки.

В общем, не вижу я для себя никаких существенных плюсов.

Кроме того, когда ты успешный наемный работник в успешной компании, финансовые затраты на увлечения считаются несколько иначе, чем когда ты пытаешься делать свой проект за свои кровные. Поэтому на вопрос, стоит ли за $2500 переходить на 24Mpx камеру у которой минимальная выдержка всего 1/4000, а X-Sync всего 1/200, сразу же находится однозначный ответ: нет! :)

Вообще, походу, если рано или поздно придется менять D700 на что-то более современное, то самым привлекательным вариантом для меня выглядит сейчас (как бы парадоксально это не звучало) вот такой комплект: тушка Nikon D3300 + мануальные Voigtlander Nokton 58mm/1.4 (эквивалент 85mm на FF) и Voigtlander Color-Skopar 28mm/2.8 (эквивалент 42mm на FF). Для того, что и как я снимаю, это будет оптимальный вариант по деньгам и качеству. Правда, не уверен, сможет ли D3300 правильно определять экспозицию с такими объективами, или же придется снимать полностью в ручном режиме.

Либо же совсем другой вариант: переход во вражеский лагерь. Есть же нормальные производители, которые не гонятся за мегапикселями. Тот же Fujifilm к примеру. Тем более, что они классный объектив 56mm/1.2 выпустили (а сейчас и его более крутой брат аннонсируется -- 56mm/1.2 APO). Какой-нибудь Fuji X-E2+XF56mm/1.2R с парой запасных аккумуляторов... Чем не вариант. Или вот совсем новенький X100T (со скоростью затвора 1/32000sec, т.е. можно спокойно снимать на полностью открытой дырке даже в яркий солнечный день)? И ведь всего 16Mpx...

Мечтательно: а ведь какого качества (ISO, ДД) можно было бы достичь на матрицах, если бы производители не стремились упаковать туда все больше и больше ячеек. Вот Sony A7S разместила на FF-сенсоре всего 12Mpx и получила ISO 409600. Еще раз: ISO четыреста девять тысяч!!!

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

[prog.flame] Статья "Type Driven Wire Protocols with Boost Fusion"

На RSDN нашел ссылку на статью Type Driven Wire Protocols with Boost Fusion. О разработке библиотеки парсинга некого транспортного протокола (уж не ISO ли 8583?) посредством Boost.Fusion. Т.е. когда посредством шаблонной и макросной магии из метаописания структуры данных прямо в C++ коде получается код для сериализации и десериализации сообщений.

В финансовом секторе, блин, работают ну очень умные ребята. То OCaml с Haskell-ем им крайне необходим. То вот байтовый поток с помощью Boost.Fusion распарсить нужно.

Был бы я помоложе, наверняка бы почувствовал свою ущербность и никчемность. Мне бы мозгов не хватило такие навороты придумывать. Максимум, на что был способен -- это сделать генерацию C++ кода из Ruby. Да и то, в свои лучшие годы. Сейчас бы и это для меня было недостижимой высотой.

По факту мне больше всего интересно, а сколько времени вся эта хрень у людей компилируется?

[prog] semver.org (Semantic Versioning) и удобная для меня система нумерации версий

Случайно наткнулся на вот такую штуку: Semantic Versioning. Это описание "стандарта" на систему нумерации версий программного компонента. С переводом на несколько языков, в том числе и на русский (перевод я не читал, о его качестве судить не берусь).

Собственно, там описывается очень старая идея. Которая до этого описывалась разными авторами под разным соусом, в том числе и мной. Если никогда не приходилось слышать про идею, стоящую за системой нумерации версий из трех буквцифр, то бишь X.Y.N, то имеет смысл прочитать этот стандарт. Тем более, что там прошли достаточно далеко в плане формализации.

Сам же я пришел к выводу, что система из четырех чисел в номере версии удобнее. А если кому-то нужно пять или шесть чисел, то это так же имеет право на жизнь.

Самое старшее число в номере -- это номер поколения библиотеки/компонента. Номера поколений показывают настолько большие различия в API и/или принципах работы компонента, что в рамках одного проекта появляется возможность задействовать сразу два или более поколения одного и того же компонента. Например, в моей практике были случаи, когда в больших проектах мирно соиспользовались cls-2 и cls-3, oess-1 и oess-2, so-4 и so-5.

Следующее число -- это мажорный номер версии. Он описывает совместимость версий компонента внутри поколения. Например, so-5.5 не вполне совместим с so-5.4 и для перехода с 5.4 на 5.5 нужно будет приложить некоторые усилия.

Следующее число -- это минорный номер версии. Его изменение говорит о том, что в компонент вносились какие-то изменения/модификации, но совместимость с предыдущими версиями сохранилась. Так, переход с 5.4.0 к 5.4.1 должен быть безболезненным. В теории :)

Следующее число -- это номер патча. Его изменение говорит о том, что в компоненте исправлена какая-то проблема, но никаких несовместимостей это не породило. Т.е. номера 5.4.0.1 и 5.4.0.2 говорят об исправлении багов в версии 5.4.0, но не о внесении в 5.4.0 каких-то новых возможностей.

Вот так и формируется система generation.major.minor.patch.

Вполне могу допустить, что кому-то четырех чисел будем мало и он захочет добавить что-то еще. Например, номер ревизии в репозитории и номер-билда или дату-время-сборки. Чтобы получилось что-то вроде 5.4.0.1.865.201409112315.

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

Например, сделана версия 5.4.0, она оказалась удачной и начала использоваться в десятке других компонентов. После чего появилась версия 5.5.0. Но переход на нее не происходит в одночасье. Какое-то время версии 5.4.0 и 5.5.0 вынуждены сосуществовать и разработчикам нужно править баги, откуда появляются 5.4.0.1, 5.4.0.2 и т.д.

Отдельный вопрос -- это изменение зависимостей компонента. Например, в версии 5.4.0 использовалась библиотека LockFreeDataStructs-1.5.0, затем вышла улучшенная версия LockFreeDataStructs-1.6.0. В результате перевода компонента на нее появилась версия 5.4.1 (ведь нужно как-то в номере версии отразить изменение зависимостей?). Однако, при попытке задействовать 5.4.1 в каком-то из проектов выяснилось, что LockFreeDataStructs-1.6.0 не дружит другой сторонней библиотекой FineGrainedParallelism-2.30.46.15. И для этого проекта пока не остается ничего другого, как оставаться на 5.4.0.2.

Между тем в 5.4.1 находятся новые баги, которые приводят к появлению версий 5.4.1.1, 5.4.1.2. И, весьма вероятно, такие же баги находятся и в 5.4.0, что ведет к 5.4.0.3, 5.4.0.4 и т.д. При этом параллельно живет и развивается версия 5.5.0, в которой так же исправляются баги и появляются версии 5.5.0.1, 5.5.0.2, ...

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

четверг, 11 сентября 2014 г.

[prog.c++] Кстати о реализации спинлоков на основе стандартной библиотеки C++11

У меня внутри SObjectizer валяется, по-сути, полностью автономная, header-only библиотека с реализацией single-reader/single-writer и multi-reader/single-writer спинлоков: spinlocks.hpp.

Базируется она на информации из документации к стандартной библиотеке C++11, идеях Дмитрия Вьюкова (так же известного как remark, одного из ведущих разработчиков Thread Sanitizer, невероятно крутого гуру в области многопоточности), исходных текстов LLVM и libcds. Собственно, код rw_spinlock, это калька с реализации аналогичного спинлока Димы из LLVM.

Так вот, если у кого-то будет интерес, то можно будет выделить spinlocks.hpp в отдельный подпроект, снабдить его примерами, более развернутой документаций. И публиковать ее релизы и дистрибутивы рядом, но отдельно от SObjectizer-а. Получится такая легковесная библиотека со spinlock-ами, базирующаяся только на стандартной библиотеке C++11, без дополнительных внешних зависимостей.

Итак, интересно/нужно это кому-то?

Если интересно или нужно, то какое имя будет подходить этой библиотеке? Например, stdcxx_spinlocks/stdcxx_spins/cxx11_spinlocks/cxxspinlocks?

[prog.с++] Продолжение про реализации таймеров. Идентификация таймеров

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

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

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

Для чего нужна идентификация заявок?

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

В процессе разбирательства с тем, как реализуются таймерные нити, я обнаружил, что используются два способа идентификации. Вполне очевидных, если подумать :)

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

Такой подход характерен для чистых C-шных библиотек. Там, как я видел, таймер -- это просто экземпляр какой-то структуры, который должен быть создан самим пользователем. Например, в libuv:

uv_timer_t timer; // Фактически, выделили память под таймер.
uv_timer_init(uv_default_loop(), &timer); // Инициализировали таймер.
uv_timer_start(&timer, timer_cb, 50, 0); // Запустили таймер.

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

Кроме того, подход на основе прямой идентификации, когда пользователь сам отвечает на создание и удаление объектов-таймеров, можно вообще обойтись без использования динамической памяти внутри таймерной нити и ее структур данных.

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

Второй способ -- это косвенная идентификация. Т.е. пользователь не отвечает за создание у себя экземпляра таймерной заявки и за контроль времени ее жизни. Вместо этого таймерную заявку создает сама таймерная нить, а пользователю возвращается какой-то обезличенный и непрозрачный идентификатор. Например, 64-битовое целое число.

Подход с косвенной идентификацией используется, например, в ACE Framework. Там идентификатором таймера является целое число типа long. Это число возвращается методом schedule. А что за ним скрывается пользователь не видит и не знает.

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

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

Если объект таймера принадлежит пользователю, то в таком сценарии нет ничего страшного. Как правило в таймере есть какие-то ссылки, описывающие расположение таймера внутри структуры данных таймерной нити. Например, если нить работает по принципу timer_list, то это будут ссылки на следующий и предыдущий элементы списка таймеров. Так вот, при изъятии таймера из своей структуры данных таймерной нити достаточно просто занулить данные ссылки или же в самом таймере выставить какой-то признак, что он не активен.

А вот при косвенной идентификации ситуация сложнее. Например, нить таймера реализована так, что идентификатором таймера является индекс в каком-то внутреннем массиве. Поскольку массив этот конечен, а таймеров приложение может создавать за время своей работы сотни миллионов, а то и миллиарды раз, то очевидно, что идентификаторы таймеров будут переиспользоваться. Что означает, что когда кто-то пытается удалить таймер с идентификатором X, существует ненулевая вероятность, что этот идентификатор уже был ранее освобожден и затем выдан совсем другому таймеру.

Насколько вероятен такой сценарий сказать сложно. Тем не менее, прямая идентификация этой проблемы не имеет вообще.

Зато косвенная идентификация позволяет скрыть внутри таймерной нити конкретный способ обслуживания таймеров. Ведь для каждого механизма обслуживания нужна своя структура таймерной заявки. Например, для timer_wheel в заявке нужно хранить номер позиции в "колесе", количество оставшихся полных оборотов "колеса", ссылки на соседей по текущей позиции в колесе. А вот для timer_list нужно иметь точное время срабатывания заявки и ссылки на соседей в списке. Для timer_set (где базовой структурой будет упорядоченное двоичное дерево, например rb-tree) -- точное время срабатывание, ссылки на родительский узел, на дочерние узлы + еще какая-то специфическая для типа дерева информация (скажем, цвет узла для rb-tree).

При использовании косвенной идентификации вся эта кухня может быть скрыта от пользователя. Наружу может быть выставлен один унифицированный интерфейс таймерной нити, а использующийся внутри механизм может настраиваться параметрами при создании таймерной нити. Пользователю будет без разницы, как именно обслуживаются его таймеры, он всегда будет работать с одним и тем же типом идентификаторов. Именно такой подход взят за основу таймеров в ACE Framework, где за единым фасадом скрыта целая куча реализаций: timer_wheel, timer_list, timer_heap и timer_hash.

С другой стороны, косвенность приводит к дополнительным накладным расходам. Т.к. таймерная нить вынуждена поддерживать, как минимум, две параллельные структуры данных: какой-то словарь/массив идентификаторов, откуда ведут ссылки на конкретную структуру с таймерными заявками (wheel, list, set, heap и т.д.). Соответственно, для того, чтобы обратиться к таймеру по его идентификатору, нужно пройти по двум структурам. Очевидно, что это не бесплатно.

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

Еще одна забавная проблема, которая есть при косвенной идентификации. Допустим, мы хотим создать периодический таймер, который сам себя деактивирует. Как-то вот так:

timer_thread tt;
tt.start();
timer_id id = tt.schedule(
  // Пауза перед первым срабатыванием.
  std::chrono::milliseconds(1),
  // Период повтора.
  std::chrono::milliseconds(3),
  // Само действие для таймера.
  [&]() {
    if( /* какое-то условие */ )
      tt.cancel(id);
  } );
...
tt.join();

Как вы думаете, все ли здесь нормально? :)

Конечно же нет ;) В зависимости от условий, пауза в 1ms может быть настолько маленькой, что за это время не успеет произойти возврат из schedule, а таймер уже сработает и внутри его обработчика уже может быть вызван метод cancel(). Но будет ли в переменной id актуальное значение идентификатора таймера? Понятное дело, что это косяк конкретной реализации таймера, а не подхода на основе косвенной идентификации, но показатель того, что думать нужно :)

Вот в общих чертах получается какая-то такая картинка. Как говорится, "выбирай, но осторожно, но выбирай" :)

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

using namespace timertt;
timer_list_thread_t tt;
tt.start();

timer_holder_t id = tt.allocate();
tt.activate(
  id,
  std::chrono::milliseconds(1),
  std::chrono::milliseconds(3),
  [&]() {
    if( /* проверка какого-то условия */ )
      tt.deactivate(id);
  } );
...
tt.join();

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

Все это дело реализовано в небольшой header-only библиотечке timertt, состоящей всего из одного заголовочного файла. Создавалась эта библиотека для SObjectizer и ее исходники лежат в том же репозитории, но к SObjectizer она не привязана и может использоваться сама по себе.

На данный момент в timertt реализовано два механизма: timer_wheel (шаблон timer_wheel_thread_template_t) и timer_list (шаблон timer_list_thread_template_t). Сама библиотека в состоянии стабильной бета-версии, на днях опробовал ее в новой ветке SObjectizer-а, выбросив ACE-овские таймеры. За выходные хочу реализовать еще timer_set или timer_heap, после чего можно будет сделать и отдельный релиз этой библиотеке. Пока же желающие могут глянуть в репозиторий, правда код еще "не причесан" и нет примеров с документаций, только тесты.

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

Пожалуйста, не делайте собственных реализаций таймеров, если на это у вас нет ну очень веских и уникальных причин. Если вы уже используете в своих проектах Boost, ACE, libuv, libev/libevent или еще какую-то серьезную библиотеку, в которой есть свои таймеры, то используйте их. Спать будете гораздо спокойнее. Если же вы не хотите тащить в свой проект ничего из вышеперечисленного, поищите какую-нибудь легковесную, но готовую библиотеку. Например, timertt ;)

Я вполне серьезно, кроме шуток. За время реализации timertt еще больше проникся уважением к разработчикам ACE Framework. Мощную штуку они сделали, труда в нее было вложено немало. Жаль только, что она настолько монстрообразная и несколько устаревшая морально. Если бы не желание превратить ядро SObjectizer в маленькую библиотеку с минимумом внешних зависимостей, реализация собственных таймеров взамен ACE-овских была бы совершенно неоправданной.

понедельник, 8 сентября 2014 г.

[prog.bugs] std::chrono::steady_clock в MSVC++2013 нифига не steady

Обыдно, блин. std::chrono::steady_clock, который даже по документации от M$ должен быть steady и monotonic, на самом деле, в MSVC++2013 нифига не steady и не monotonic. Как следствие, и такие функции, как std::this_thread::sleep_for или std::condition_variable::wait_until.

Тогда как тоже самое в MinGW-w64 (GCC 4.9.1 в варианте Posix) работает именно так, как нужно. Т.е. и steady, и monotonic.

И таки да, в Интернетах про эту проблему VC++ пишут, но читаешь такие вещи, только когда сам наступишь. Говорят, что в MSVC++2014 уже исправят.

Для желающих проверить маленькая самодостаточная программка. Она должна работать две минуты. Если в процессе ее работы менять время на компьютере вперед или назад, на программу это не должно оказывать влияния.

[prog.flame] Интересный критерий

Найдено вот тут. Цитата из Сергея Зефирова (aka thesz):

В языке присутствует/отсутствует некая возможность, позволяющая мне сократить количество ошибок.

Неформальная прикидка - могу ли я программировать на этом языке пьяным. На Хаскеле и тикле могу, на С, C#, C++ - нет. На Хаскеле я могу писать пьяным сложные вещи, типа решателей задач булевской выполнимости, на тикле - простые, типа удалить ^M на линуксе. На C.* - даже простых написать не смогу.

Алкоголь подавляет высшую нервную деятельность, поэтому он является усилителем моих ошибок.

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

Про себя же отмечу, что вряд ли я лично отнесу программирование "решателей задач булевской выполнимости" к программной инженерии. Тут какая-то тонкая грань между искусством/ремеслом и наукой/инженерией. Разница, наверное, в повторяемости и воспроизводимости. Наверное, людям, которые занимаются улучшением вычислительных методов расчета прочности ленточных фундаментов (вспомнилось из студенческих времен) необходимо разрабатывать ПО. Но это будет штучное ПО для штучной задачи. Возможно, какой-то инструментарий и какие-то методики из индустрии разработки ПО вполне подойдут для этой цели. Но, в целом, индустрия активно отбирает и развивает, либо, напротив, отбраковывает и выбрасывает, инструменты и приемы для другого класса задач.

воскресенье, 7 сентября 2014 г.

[prog.flame] Понапишут же такого...

Наткнулся на прекрасное: ...реализуемые неявно явными синтаксическими конструкциями....

Неявно явными -- это пять!

Само предложение, из которого выдернут этот фрагмент, так же доставляет (орфография и пунктуация оригинала):

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

Ну а дальше феерическое: У меня после таких исследований появилось впечтление что С++ — это какой-то очередной мыльный пузырь, раздутый N-ым количество академиков.

Как только C++ не называли в священных форумных войнах. Но вот мыльным пузырем, по-моему, впервые. Да и как-то сложно плюсы мыльным пузырем называть. Скоро уж тридцатник стукнет.