// // Created by jonathan.hanks on 3/26/20. // #ifndef DAQD_TRUNK_DAQD_THREAD_HH #define DAQD_TRUNK_DAQD_THREAD_HH #include <functional> #include <mutex> #include <vector> #include <pthread.h> using thread_action_t = std::function< void( void ) >; /*! * @brief A more generic interface around pthread_create * @details pthread_create has a limited interface, pass a void *, * std::thread doesn't allow specifying stack size, scheduling, ... * so provide a wrapper on pthread create that takes a more generic * argument * @param tid The pthread_t thread id structure * @param attr Structure describing pthread attributes to set * @param handler the code to run on the new thread */ extern int launch_pthread( pthread_t& tid, const pthread_attr_t& attr, thread_action_t handler ); /*! * @brief wrap the pthread scope macros in a enum to * bring them into the C++ type system. */ enum class thread_scope_t { SYSTEM = PTHREAD_SCOPE_SYSTEM, PROCESS = PTHREAD_SCOPE_PROCESS, }; /*! * @brief a strong type for a stacksize. */ class thread_stacksize_t { public: explicit thread_stacksize_t( std::size_t size ) : size_( size ) { } thread_stacksize_t( const thread_stacksize_t& ) = default; thread_stacksize_t& operator=( const thread_stacksize_t& ) throw( ) = default; thread_stacksize_t& operator=( const std::size_t new_size ) throw( ) { size_ = new_size; return *this; } std::size_t get( ) const { return size_; } private: std::size_t size_; }; /*! * @brief a RAII wrapper for pthread_attr_t, ensuring that it is always * destroyed. The constructor also uses strong types to all an arbitrary * set of attributes to be set at construction time. */ class thread_attr_t { public: thread_attr_t( ) : attr_{} { pthread_attr_init( &attr_ ); } /*! * @brief construct a thread_attr_t and set attributes on it. * @tparam T one of the attribute types that can be set * @tparam Args list of additional attribute types * @param t the first attribute to set * @param args additional (0 or more) attributes to set */ template < typename T, typename... Args > explicit thread_attr_t( T t, Args... args ) : attr_{} { pthread_attr_init( &attr_ ); set( t, args... ); } thread_attr_t( const thread_attr_t& ) = delete; thread_attr_t( thread_attr_t&& ) = delete; ~thread_attr_t( ) { pthread_attr_destroy( &attr_ ); } thread_attr_t& operator=( const thread_attr_t& ) = delete; thread_attr_t& operator=( thread_attr_t&& ) = delete; /*! * @brief set the PTHREAD_SCOPE value * @param scope the scope value as an enum */ void set( thread_scope_t scope ) { pthread_attr_setscope( &attr_, static_cast< int >( scope ) ); } /*! * @brief set the stack size * @param stack_size the size of the stack */ void set( thread_stacksize_t stack_size ) { pthread_attr_setstacksize( &attr_, stack_size.get( ) ); } /*! * @brief take an arbitrary list of attribute types and set them. * @tparam T First attribute type to set * @tparam Args Additional (0+) types of attributes * @param t the first attribute * @param args additional attributes */ template < typename T, typename... Args > void set( T t, Args... args ) { set( t ); set( args... ); } pthread_attr_t& at( ) { return attr_; } pthread_attr_t* get( ) { return &attr_; } private: pthread_attr_t attr_; }; /*! * @brief a container of threads, that ensure that each thread is joined * @details The handler is given a stop function which is called (once) to stop * all the threads that are associated with it. It is the responsibility of the * caller to ensure that the stop function will indeed stop all the threads that * will be managed by the thread_handler_t. */ class thread_handler_t { public: using stop_function_t = std::function< void( void ) >; explicit thread_handler_t( stop_function_t stopper ) : stopper_{ std::move( stopper ) } {}; thread_handler_t( const thread_handler_t& ) = delete; thread_handler_t( thread_handler_t&& ) = delete; thread_handler_t& operator=( const thread_handler_t ) = delete; thread_handler_t& operator=( thread_handler_t&& ) = delete; ~thread_handler_t( ); void push_back( thread_action_t action, thread_attr_t& attr ) { std::lock_guard< std::mutex > l_{ m_ }; std::vector< pthread_t > ids( thread_ids_.begin( ), thread_ids_.end( ) ); ids.emplace_back( ); if ( launch_pthread( ids.back( ), attr.at( ), std::move( action ) ) != 0 ) { throw std::runtime_error( "Unable to create thread" ); } thread_ids_.swap( ids ); } void clear( ); private: std::vector< pthread_t > thread_ids_{}; std::mutex m_{}; stop_function_t stopper_; }; #endif // DAQD_TRUNK_DAQD_THREAD_HH