пятница, 17 июля 2015 г.

[prog.c++.flame] Очередной CodeSize Battle: Just::Thread Pro vs SObjectizer-5.5.6

Недавно Энтони Уильямс напомнил о своей библиотеке Just::Thread Pro, в которой есть поддержка акторов. И привел классический пример с парикмахером, который спит, пока нет посетителей и просыпается, когда посетители появляются. Под катом показаны реализации на Just::Thread Pro и на SO-5.5.6.

Примечание. Реализация на SO-5.5.6 не 1-в-1, но очень близко соответствует версии Уильямса.

#include <jss/actor.hpp>
#include <iostream>
#include <chrono>
#include <thread>
#include <stdlib.h>
#include <memory>
#include <sstream>

struct customer_waiting
{
    jss::actor_ref customer;
};

struct customer_enters
{
    jss::actor_ref customer;
};

struct customer_leaves
{};

struct start_haircut
{};

struct done_haircut
{};

struct closing_time
{};

struct no_room
{};

struct shop_closed
{};

struct no_customers
{};

void logger_func(){
    for(;;){
        jss::actor::receive()
            .match<std::string>([](std::string s){
                    std::cout<<s<<std::endl;
                });
    }
}

jss::actor logger(logger_func);

void barber_func()
{
    bool go_home=false;
    unsigned haircuts=0;
    
    while(!go_home)
    {
        logger<<std::string("barber is sleeping");
        bool can_sleep=false;
        do
        {
            jss::actor::receive()
                .match<customer_waiting>(
                    [&](customer_waiting c){
                        logger<<std::string("barber is cutting hair");
                        c.customer<<start_haircut();
                        std::this_thread::sleep_for(std::chrono::milliseconds(1000*(1+(rand()%5))));
                        c.customer<<done_haircut();
                        ++haircuts;
                    })
                .match<no_customers>(
                    [&](no_customers){
                        can_sleep=true;
                    })
                .match<closing_time>(
                    [&](closing_time){
                        go_home=true;
                    });
        }
        while(!can_sleep && !go_home);
    }
    std::ostringstream os;
    os<<"barber is going home. He did "<<haircuts<<" haircuts today";
    logger<<os.str();
}

void shop_func(jss::actor_ref barber)
{
    bool closed=false;
    unsigned waiting_customers=0;

    unsigned const max_waiting_customers=3;

    std::ostringstream os;
    logger<<std::string("shop opens");
   
    while(!closed)
    {
        jss::actor::receive()
            .match<customer_enters>(
                [&](customer_enters c){
                    ++waiting_customers;
                    os.str("");
                    os<<"shop has "<<waiting_customers<<" customers";
                    logger<<os.str();
                    if(waiting_customers<=max_waiting_customers){
                        barber<<customer_waiting{c.customer};
                    } else
                        c.customer<<no_room();
                })
            .match<customer_leaves>(
                [&](customer_leaves){
                    if(!--waiting_customers)
                    {
                        logger<<"last customer left shop";
                        barber<<no_customers();
                    } else{
                        os.str("");
                        os<<"shop has "<<waiting_customers<<" customers";
                        logger<<os.str();
                    }
                })
            .match<closing_time>(
                [&](closing_time c){
                    logger<<std::string("shop closing");
                    closed=true;
                    barber<<c;
                });
    }
    while(waiting_customers){
        os.str("");
        os<<"shop has "<<waiting_customers<<" customers";
                        logger<<os.str();
        jss::actor::receive()
            .match<customer_enters>(
                [&](customer_enters c){
                    ++waiting_customers;
                    logger<<"customer turned away because shop closed";
                    c.customer<<shop_closed();
                })
            .match<customer_leaves>(
                [&](customer_leaves){
                    if(!--waiting_customers)
                    {
                        logger<<"last customer left shop";
                    }
                });
    }
    
    logger<<std::string("shop closed");
}

enum class haircut_status{
    had_haircut,no_room,shop_closed
        };

haircut_status try_and_get_hair_cut(unsigned customer,jss::actor_ref shop){
    std::ostringstream os;
    os<<"customer "<<customer<<" goes into barber shop";
    logger<<os.str();
    try{
        shop<<customer_enters{jss::actor::self()};
    }
    catch(jss::no_actor){
        os.str("");
        os<<"customer "<<customer<<" finds barber shop is closed";
        logger<<os.str();
        return haircut_status::shop_closed;
    }
    
    haircut_status status=haircut_status::no_room;
    
    jss::actor::receive()
        .match<start_haircut>(
            [&](start_haircut)
            {
                os.str("");
                os<<"customer "<<customer<<" is having a haircut";
                logger<<os.str();
                jss::actor::receive()
                    .match<done_haircut>(
                        [&](done_haircut)
                        {
                            os.str("");
                            os<<"customer "<<customer<<" is done having a haircut";
                            logger<<os.str();
                        }
                        );
                status=haircut_status::had_haircut;
            }
            )
        .match<no_room>(
            [&](no_room)
            {
                os.str("");
                os<<"customer "<<customer<<" leaves because there is no room";
                logger<<os.str();
                status=haircut_status::no_room;
            }
            )
        .match<shop_closed>(
            [&](shop_closed)
            {
                os.str("");
                os<<"customer "<<customer<<" finds barber shop is closed";
                logger<<os.str();
                status=haircut_status::shop_closed;
            }
            );
    os.str("");
    os<<"customer "<<customer<<" leaves barber shop";
    logger<<os.str();
    try{
        shop<<customer_leaves();
    }
    catch(jss::no_actor){
    }
    return status;
}


void customer_func(unsigned i,jss::actor_ref shop)
{
    std::ostringstream os;
    os<<"customer "<<i<<" goes to town";
    logger<<os.str();
    
    haircut_status status;
    do{
        os.str("");
        os<<"customer "<<i<<" is shopping";
        logger<<os.str();
        std::this_thread::sleep_for(std::chrono::milliseconds(500*(rand()%20)));
    }
    while((status=try_and_get_hair_cut(i,shop))==haircut_status::no_room);
    os.str("");
    os<<"customer "<<i<<" is going home";
    logger<<os.str();
}

int main()
{
    {
        jss::actor barber(barber_func);
        jss::actor barbershop(shop_func,jss::actor_ref(barber));
        
        unsigned const count=20;
        jss::actor customers[count];
        
        for(unsigned i=0;i<count;++i)
        {
            std::ostringstream os;
            
            os<<"Starting customer "<<i;
            logger<<os.str();
            
            customers[i]=jss::actor(customer_func,i,jss::actor_ref(barbershop));
        }
        
        std::this_thread::sleep_for(std::chrono::seconds(20));
        barbershop<<closing_time();
    }
    logger.stop();
}
#include <iostream>
#include <cstdlib>

#include <so_5/all.hpp>

using log_msg = so_5::rt::tuple_as_message_t< so_5::rt::mtag<0>, std::string >;

struct customer_waiting : public so_5::rt::message_t
{
   so_5::rt::mbox_t customer;

   customer_waiting( so_5::rt::mbox_t c ) : customer( std::move(c) ) {}
};

struct customer_enters : public so_5::rt::message_t
{
   so_5::rt::mbox_t customer;

   customer_enters( so_5::rt::mbox_t c ) : customer( std::move(c) ) {}
};

struct customer_leaves : public so_5::rt::signal_t {};

struct start_haircut : public so_5::rt::signal_t {};

struct done_haircut : public so_5::rt::signal_t {};

struct closing_time : public so_5::rt::signal_t {};

struct no_room : public so_5::rt::signal_t {};

struct shop_closed : public so_5::rt::signal_t {};

struct no_customers : public so_5::rt::signal_t {};

so_5::rt::mbox_t
make_logger( so_5::rt::agent_coop_t & coop )
{
   auto logger = coop.define_agent();
   logger.event( logger.direct_mbox(), []( const log_msg & msg ) {
         std::cout << std::get<0>(msg) << std::endl;
      } );
   return logger.direct_mbox();
}

templatetypename A >
inline void operator<<=( const so_5::rt::mbox_t & to, A && a )
{
   so_5::send< log_msg >( to, std::forward< A >(a) );
}

struct msg_maker
{
   std::ostringstream m_os;

   templatetypename A >
   msg_maker & operator<<( A && a ) 
   {
      m_os << std::forward< A >(a);
      return *this;
   }
};

inline void operator<<=( const so_5::rt::mbox_t & to, msg_maker & maker )
{
   to <<= maker.m_os.str();
}

class a_barber_t : public so_5::rt::agent_t
{
   const so_5::rt::mbox_t m_log;
   unsigned m_haircuts = 0;

public :
   a_barber_t( context_t ctx, so_5::rt::mbox_t log )
      :  so_5::rt::agent_t( ctx )
      ,  m_log( std::move( log ) )
   {}

   virtual void so_define_agent() override
   {
      so_default_state()
         .event( [&]( const customer_waiting & evt ) {
               m_log <<= "barber is cutting hair";
               so_5::send< start_haircut >( evt.customer );
               std::this_thread::sleep_for(
                     std::chrono::milliseconds( 1000 * (1+(rand()%5)) ) );
               so_5::send< done_haircut >( evt.customer );
               ++m_haircuts;
            } )
         .event< no_customers >( [&] {
               m_log <<= "baber is sleeping";
            } )
         .event< closing_time >( [&] {
               m_log <<= msg_maker() << "barber is going home. He did "
                  << m_haircuts << " haircuts today";
            } );
   }

   virtual void so_evt_start() override
   {
      m_log <<= "barber is sleeping";
   }
};

class a_shop_t : public so_5::rt::agent_t
{
   const so_5::rt::mbox_t m_log;
   const so_5::rt::mbox_t m_barber;

   unsigned m_waiting_customers = 0;
   const unsigned m_max_waiting_customers = 3;

   const so_5::rt::state_t st_open = so_make_state( "open" );
   const so_5::rt::state_t st_closing = so_make_state( "closing" );
   const so_5::rt::state_t st_closed = so_make_state( "closed" );

public :
   a_shop_t( context_t ctx, so_5::rt::mbox_t log, so_5::rt::mbox_t barber )
      :  so_5::rt::agent_t( ctx )
      ,  m_log( std::move( log ) )
      ,  m_barber( std::move( barber ) )
   {}

   virtual void so_define_agent() override
   {
      this >>= st_open;

      st_open
         .event( [&]( const customer_enters & evt ) {
               ++m_waiting_customers;
               m_log <<= msg_maker() << "shop has " << m_waiting_customers << " customers";
               if( m_waiting_customers <= m_max_waiting_customers )
                  so_5::send< customer_waiting >( m_barber, evt.customer );
               else
                  so_5::send< no_room >( evt.customer );
            } )
         .event< customer_leaves >( [&] {
               if( !--m_waiting_customers )
               {
                  m_log <<= "last customer left shop";
                  so_5::send< no_customers >( m_barber );
               }
               else
                  m_log <<= msg_maker() << "shop has " << m_waiting_customers << " customers";
            } )
         .event< closing_time >( [&] {
               this >>= st_closing;
               m_log <<= "shop closing";
               so_5::send< closing_time >( m_barber );
            } );

      st_closing
         .event( [&]( const customer_enters & evt ) {
               ++m_waiting_customers;
               m_log <<= "customer turned away because shop closed";
               so_5::send< shop_closed >( evt.customer );
            } )
         .event< customer_leaves >( [&] {
               if( !--m_waiting_customers )
               {
                  m_log <<= "last customer left shop";
                  this >>= st_closed;
                  m_log <<= "shop closed";

                  so_deregister_agent_coop_normally();
               }
            } );
   }

   virtual void so_evt_start() override
   {
      m_log <<= "shop opens";
   }
};

class a_customer_t : public so_5::rt::agent_t
{
   const unsigned m_id;
   const so_5::rt::mbox_t m_log;
   const so_5::rt::mbox_t m_shop;

   struct start_shopping : public so_5::rt::signal_t {};
   struct finish_shopping : public so_5::rt::signal_t {};

   void leave_baber_shop()
   {
      m_log <<= msg_maker() << "customer " << m_id << " leaves baber shop";
      so_5::send< customer_leaves >( m_shop );
   }

   void go_home()
   {
      m_log <<= msg_maker() << "customer " << m_id << " is going home";
   }

public :
   a_customer_t( context_t ctx, unsigned id,
      so_5::rt::mbox_t log, so_5::rt::mbox_t shop )
      :  so_5::rt::agent_t( ctx )
      ,  m_id( id )
      ,  m_log( std::move( log ) )
      ,  m_shop( std::move( shop ) )
   {}

   virtual void so_define_agent() override
   {
      so_default_state()
         .event< start_shopping >( [&] {
               m_log <<= msg_maker() << "customer " << m_id << " is shopping";
               so_5::send_delayed_to_agent< finish_shopping >( *this,
                  std::chrono::milliseconds( 500 * (rand()%20) ) );
            } )
         .event< finish_shopping >( [&] {
               m_log <<= msg_maker() << "customer " << m_id << " goes into barber shop";
               so_5::send< customer_enters >( m_shop, so_direct_mbox() );
            } )
         .event< start_haircut >( [&] {
               m_log <<= msg_maker() << "customer " << m_id << " is having a haircut";
            } )
         .event< done_haircut >( [&] {
               m_log <<= msg_maker() << "customer " << m_id << " is done having a haircut";

               leave_baber_shop();
               go_home();
            } )
         .event< no_room >( [&] {
               m_log <<= msg_maker() << "customer " << m_id << " leaves because there is no room";

               leave_baber_shop();
               so_5::send_to_agent< start_shopping >( *this );
            } )
         .event< shop_closed >( [&] {
               m_log <<= msg_maker() << "customer " << m_id << " finds barber shop is closed";

               leave_baber_shop();
               go_home();
            } );
   }

   virtual void so_evt_start() override
   {
      so_5::send_to_agent< start_shopping >( *this );
   }
};

int main()
{
   try
   {
      so_5::launch( []( so_5::rt::environment_t & env ) {
         env.introduce_coop( [&]( so_5::rt::agent_coop_t & coop ) {
            auto logger = make_logger( coop );

            auto barber = coop.make_agent_with_binder< a_barber_t >(
                  so_5::disp::one_thread::create_private_disp( env )->binder(),
                  logger )->so_direct_mbox();

            auto barbershop = coop.make_agent< a_shop_t >(
                  logger, barber )->so_direct_mbox();

            unsigned const count = 20;
            forunsigned i = 0; i != count; ++i )
               coop.make_agent< a_customer_t >( i, logger, barbershop );

            so_5::send_delayed< closing_time >( env, barbershop,
                  std::chrono::seconds( 20 ) );
         } );
      } );

      return 0;
   }
   catchconst std::exception & x )
   {
      std::cerr << "Exception: " << x.what() << std::endl;
   }

   return 2;
}

В SObjectizer-овской версии есть небольшой читтинг :)

Дабы не писать постоянно so_5::send<log_msg>(m_log,something) и не колупаться с обнулением содержимого std::ostringstream (что довольно часто происходит в версии Уильямса), в SObjectizer-овской версии было переопределено несколько вариантов operator<<=. Но, т.к. определение этих вспомогательных штук в коде присутствует и объем кода несколько раздувает, то это не такое уж и большое читтерство :)

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