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

[prog.c++14] Мечты сбываются: задышала реализация диспетчера, которая может использовать пользовательские классы нитей

В рамках работ над новой версией so_5_extra разрабатывается asio_thread_pool-диспетчер. Это диспетчер, в котором на пуле потоков запускаются методы asio::io_service::run(), и агенты, которые привязаны к такому диспетчеру, отрабатывают свои события на этих же рабочих потоках. По сути, диспетчеризацией событий для таких агентов занимается asio::io_service. Что должно дать возможность агентам просто и прозрачно выполнять и IO-операции, и обработку прикладных сообщений не задумываясь о том контексте, на котором они работают (ноги у этого всего растут вот отсюда).

Но самое интересное для меня другое: это был еще и эксперимент по созданию диспетчера, который может использовать не только std::thread для создания рабочих потоков, но и предоставленный пользователем класс нити, если этот класс частично мимикрирует под std::thread. Зачем такое может потребоваться?

Ну, например, если вы хотите использовать какую-то специфическую для ОС функциональность, недоступную через std::thread. Скажем, если вам нужно назначить собственные pthread_attr_t перед запуском рабочей нити. Это не такая уж и экзотическая ситуация. Например, если вы создаете большое количество нитей в приложении, то можете захотеть уменьшить размер стека для них, т.к. размер по умолчанию для вашей задачи может быть слишком большой.

Так вот, этот новый asio_thread_pool показал, что подобный фокус вполне себе работает. И выглядит это как-то вот так:

// Definition of traits to be used with asio_thread_pool.
struct my_disp_traits
{
   // Actual type of thread to be used.
   using thread_type = my_pthread_t;
};
...
   // Create dispatcher for ring of agents.
   auto disp = asio_tp::create_private_disp<my_disp_traits>(
         coop.environment(),
         "asio_tp",
         std::move(disp_params) );

Стоить определить в traits-ах для диспетчера имя класса, который следует использовать вместо std::thread, и asio_thread_pool начнет использовать данный класс.

Любопытные последствия могут быть у этого эксперимента. У меня уже давно есть мысль о том, чтобы добавить возможность такой кастомизации ко всем штатным диспетчерам SO-5. И, поскольку эксперимент оказался удачным, такая возможность может воплотиться в реальность в SO-5.5.20, работа над которой должна начаться вот прямо после фиксации so_5_extra-1.0.2. А это означает, что реализации штатных диспетчеров вынужденно станут шаблонными. Что заметно уменьшит количество .cpp-файлов в исходниках SObjectizer-а. Что будет означать еще одним большой шаг в сторону header-only версии SO-5. При этом header-only -- это вовсе не самоцель. Но чем больше будет в SO-5 кастомизаций через шаблоны, тем ближе к header-only окажется реализация.

Под катом простейшая реализация my_pthread_t, набросанная на коленке за 15 минут...

class my_pthread_t
{
   struct data_t
   {
      pthread_t m_thread;
      std::function<void()> m_func;

      data_t( std::function<void()> func ) : m_func( std::move(func) ) {}

      void join() noexcept
      {
         pthread_join( m_thread, nullptr );
      }
   };

   std::unique_ptr<data_t> m_impl;

   static void * thread_body( void * arg )
   {
      auto * impl = reinterpret_cast<data_t*>(arg);
      impl->m_func();

      return nullptr;
   }

public :
   my_pthread_t() = default;

   template<typename F>
   my_pthread_t( F && f )
      :  m_impl( std::make_unique< data_t >( std::move(f) ) )
   {
      const auto rc = pthread_create(
            &(m_impl->m_thread),
            nullptr,
            &my_pthread_t::thread_body,
            m_impl.get() );
      if0 != rc )
         throw std::system_error(
               std::error_code(errno, std::generic_category()) );
   }

   my_pthread_t( const my_pthread_t & ) = delete;

   my_pthread_t( my_pthread_t && other ) noexcept
      :  m_impl( std::move(other.m_impl) )
   {}

   ~my_pthread_t()
   {
      join();
   }

   friend void swap( my_pthread_t & a, my_pthread_t & b ) noexcept
   {
      using std::swap;
      swap( a.m_impl, b.m_impl );
   }

   my_pthread_t & operator=( my_pthread_t && other ) noexcept
   {
      my_pthread_t tmp( std::move(other) );
      swap( *this, tmp );
      return *this;
   }

   void join() noexcept
   {
      if( m_impl )
      {
         auto impl = std::move(m_impl);
         impl->join();
      }
   }
};

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