воскресенье, 5 июня 2016 г.

[prog.c++] Попытка сделать Go-шный select и ограниченные по размеру mchain-ы...

В SObjectizer вот уже более полугода есть аналоги CSP-шных каналов под названием mchains. Первоначально с mchain-ами можно было использовать только операцию receive, которая выбирала для обработки сообщения только из одного канала. Начиная с версии 5.5.16 появилась операция select, которая позволяет выбирать сообщения из нескольких mchain-ов.

SObjectizer-овский select создавался под влиянием Go-шного select-а. Но между SObjectizer-овским и Go-шным select-ами есть важное различие: SObjectizer-овский select выполняет только receive-операции из множества mchain-ов, тогда как Go-шный select может сочетать как receive-, так и send-операции.

Сейчас появилось немного времени для работы над версией 5.5.17 и пытаюсь придумать, можно ли расширить SObjectizer-овский select send-операциями. Вот здесь был показан пример с числами Фибоначчи, который при поддержке send-операций в select-е мог бы выглядеть приблизительно вот так:

void fibonacci( mchain_t values_ch, mchain_t quit_ch )
{
   int x = 0, y = 1;
   bool must_continue = true;
   while( must_continue )
   {
      select( from_all(),
         send_case< int >( values_ch, x ).then( [&] {
               auto old_x = x;
               x = y; y = old_x + y;
            } ),
         receive_case( quit_ch, [&](quit) { must_continue = false; } );
   }
}

В процессе размышлений вышел на небольшой идеологический тупик. Дело в том, что mchain-ы в SObjectizer подразделяются на две сильно разные категории: mchain-ы ограниченного и неограниченного размера. Тогда как в Go, насколько я понимаю, у каждого канала есть размер связанного с ним буфера (даже если этот размер и не задается программистом явно).

Соответственно, первая составляющая обнаруженного тупика состоит в том, что попытка задействовать send_case для mchain-а неограниченного размера не будет иметь большого смысла. Т.к. операция отсылки сообщения в канал будет всегда завершаться без длительной блокировки отправителя. А ведь ради такой блокировки send_case и нужен в select-е: если потребитель сообщений не успевает их вычитывать, то производитель "засыпает" до тех пор, пока потребитель не справится с нагрузкой, либо пока не возникнут какие-то дополнительные события (например, активизация какого-то из receive_case). Т.е. если в показанном выше примере values_ch окажется неограниченным по размеру mchain-ом, то while с select-ом очень быстро приведет к исчерпанию свободной памяти.

Но и в случае с ограниченными по размеру mchain-ами так же не все просто. Во-первых, в свойствах mchain-а может быть задан максимальный тайм-аут ожидания выполнения send-а на полном mchain-е. Т.е., если mchain полон и кто-то вызывает send, то этот send может усыпить вызвавшую его нить на некоторое ограниченное время. Соответственно, вопрос: должно ли это время оказывать влияние на ситуацию, когда send-операция инициируется внутри select-а? Скажем, для mchain-а установлен тайм-аут ожидания в 0.15s. Тогда как при вызове select-а с send-операцией нас вполне может устроить и тайм-аут в 1.5s.

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

Так вот, применение каких-то из этих overflow_reaction для send_case из select-а может иметь нехорошие последствия. Скажем, выброс исключения -- это странное поведение, ведь select должен выполнять send_case только когда есть возможность записать сообщение в mchain. А такие реакции, как выброс самого старого и самого нового сообщения приведут к тому, что send_case всегда будет выполняться без какого-либо ожидания. Ну на самом деле, зачем ждать, если можно что-то выбросить, а на освободившееся место что-то добавить?

Как-то пока выходит, что mchain-ы, которые затачивались под работу с send-ами, из-за своей продвинутой функциональности не сильно хорошо дружат с select-ом в плане ожидания возможности отсылки сообщений. Как вариант, можно в send_case игнорировать настройки самого mchain-а (в частности тайм-аут ожидания на полном mchain-е и тип overflow_reaction). Но тогда получится, что одна и та же сущность ведет себя сильно по-разному в зависимости от того, каким инструментом с ней работают: для send-ов будет свое поведение, для send_case -- свое. Что не есть хорошо.

В общем, нужно думать.

PS. Еще один интересный вопрос. Допустим, если select с несколькими send_case-ами:

select( from_all(),
   send_case< msg1 >( ch1, ... ),
   send_case< msg2 >( ch2, ... ),
   send_case< msg3 >( ch3, ... ) );

Должен ли выход из такого select-а происходить после одного успешного send_case (любого из трех)? Или же после завершения всех трех send_case?

Комментариев нет: