Implementation of the Active Object pattern wrapping a standard C++11 thread.
- Whole implementation contained in a single header file!
- Inherit from a template and you are done. See the tiny example bellow.
- Exchange messages of any type (does not requires them to derive from a common base class)
- Messages are asynchronously delivered in the same order they were sent
- Allows to invoke callbacks on clients of unknown type (useful for libraries)
- Callbacks on the active object auto-store themselves with no boilerplate code
- Timers ability with client-driven handlers (no need for handler↔object resolving maps)
- Internal lock-free MPSC messages queue
- Extensive internal use of move semantics supporting delivery of non-copiable objects
- Several million msg/sec between each two threads (both Linux and Windows) in ordinary hardware
- The wrapped thread lifecycle overlaps and is driven by the object existence
- The object is kept alive by smart pointers (whoever has a reference can safely send messages)
- No internal strong references (only the final users determine the destruction/end)
- Nonetheless, callbacks onto already deleted active objects do not crash the application
- Mininum gcc version supported is 4.8.0 (which added the thread_local keyword)
- Works with clang 3.3 and Visual Studio 2015 Update 3 (no previous versions tested on both)
- Clean, standard C++11 (no conditional code, same implementation for all platforms)
// Linux: g++ -std=c++11 -lpthread demo.cpp -o demo// Windows: cl.exe demo.cpp #include<string> #include<iostream> #include<sstream> #include"ActorThread.hpp"structMessage{std::string description}; structOtherMessage{double beautifulness}; classConsumer : publicActorThread<Consumer>{friend ActorThread<Consumer> voidonMessage(Message& p){std::cout << "thread " << std::this_thread::get_id() << " receiving " << p.description << std::endl} voidonMessage(OtherMessage& p){std::cout << "thread " << std::this_thread::get_id() << " receiving " << p.beautifulness << std::endl} }; classProducer : publicActorThread<Producer>{friend ActorThread<Producer> std::shared_ptr<Consumer> consumer; voidonMessage(std::shared_ptr<Consumer>& c){consumer = c; timerStart(true, std::chrono::milliseconds(250), TimerCycle::Periodic); timerStart(3.14, std::chrono::milliseconds(333), TimerCycle::Periodic)} voidonTimer(constbool&){std::ostringstream report; report << "test from thread " << std::this_thread::get_id(); consumer->send(Message{report.str() })} voidonTimer(constdouble& value){consumer->send(OtherMessage{value })} }; classApplication : publicActorThread<Application>{friend ActorThread<Application> voidonStart(){auto consumer = Consumer::create(); // spawn new threadsauto producer = Producer::create(); producer->send(consumer); std::this_thread::sleep_for(std::chrono::seconds(3)); stop()} }; intmain(){returnApplication::run(); // re-use existing thread }Despite received by reference, a copy of the original object is delivered to the destination thread. Alternatively, the carried object can be moved, which is the way to transfer non-copiable objects like a unique_ptr. Copying is highly efficient with pointers, but note that several threads must not concurrently access a unsafe pointed object.
See the examples folder for more elaborated examples, including a library and its client using the callbacks mechanism.