четверг, 4 февраля 2016 г.

[prog.flame] Конкурентный HelloWorld на Go и на C++

Поиск в Google по ключевым словам "actor model C++" привел вот к этой записи в чьем-то блоге: Concurrent Hello World in Go, Erlang and C++. Go-шный вариант там выглядит весьма симпатично. Отличный показатель того, для чего затачивался язык (а так же показатель того, за пределами какой области язык не нужен). Подумалось, что и на C++ можно сделать не совсем 1-в-1, но очень близкое по стилю.

Попробовал. Кое-что получилось :)

Итак, программка создает двух параллельно работающих воркеров (в Go это гороутины, в C++ это обычные нити). Воркеры обмениваются между собой строками посредством каналов и по очереди печатают на стандартный поток вывода части сообщения "Hello World".

С++ный вариант отличается от Go-шного тем, что в нем нет третьего канала quitter. Этот канал не нужен, т.к. завершение воркеров мы можем ожидать на вызове thread::join.

Если сравнивать только функции main, то получается вот так (Upd: в C++ варианте задействованы возможности C++14; Upd2: используется функция create_mchain из SO-5.5.16):

func main() {
    sayHello := make(chan string)
    sayWorld := make(chan string)
    quitter := make(chan string)

    go func() {
        for i := 0; i < 1000; i++ {
            fmt.Print("Hello ")
            sayWorld <- "Do it"
            <-sayHello
        }
        sayWorld <- "Quit"
    }()

    go func() {
        for {
            var reply string
            reply = <-sayWorld
            if (reply == "Quit") {
                break
            }
            fmt.Print("World!\n")
            sayHello <- "Do it"
        }
        quitter <- "Done"
    }()

    <-quitter
}
int main() {
   wrapped_env_t sobj;

   auto say_hello = create_mchain( sobj );
   auto say_world = create_mchain( sobj );

   thread hello{ [&] {
      string reply;
      forint i = 0; i != 1000; ++i ) {
         cout << "Hello ";
         say_world << "Do it"s;
         say_hello >> reply;
      }
      say_world << "Quit"s;
   } };

   thread world{ [&] {
      for(;;) {
         string reply;
         say_world >> reply;
         if"Quit" == reply )
            break;
         cout << "World" << endl;
         say_hello << "Do it"s;
      }
   } };

   auto_join(hello, world);
}

В С++ном варианте задействованы mchain-ы из SObjectizer-а. Но т.к. в mchain-ах нет operator<< и operator>>, то для повышения выразительности кода потребовалось их сделать. Так что для полного сравнения нужно показать еще и то, что предшествует функции main:

package main

import "fmt"
#include <iostream>
#include <thread>

#include <so_5/all.hpp>

using namespace std;
using namespace std::literals;
using namespace so_5;

templatetypename T >
void operator<<( mchain_t & to, T && o ) {
   send< T >( to, forward<T>(o) );
}

templatetypename T >
void operator>>( mchain_t & from, T & o ) {
   receive( from, infinite_wait, [&o]( const T & msg ) { o = msg; } );
}

Так что некоторое читерство все-таки есть :) Но, с другой стороны, чем больше проект, тем больше шансов, что разные утилитарные вещи, вроде показанных выше operator<< и operator>>, будут написаны однажды и затем будут широко использоваться по всему проекту.

И в сухом остатке получается, что с правильным инструментом в C++ можно работать с CSP-шными каналами не сильно хуже, чем в Go. А может даже и лучше (учитывая функциональность mchain-ов).

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

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