Поиск в 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; for( int 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; template< typename T > void operator<<( mchain_t & to, T && o ) { send< T >( to, forward<T>(o) ); } template< typename 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 без задействования агентов.
Комментариев нет:
Отправить комментарий