I have a ะก++ class that has a databases_list_ vector defined.
This vector contains database objects.
I need to make two functions that will run on separate threads. And each function will use the databases_list_
vector.
That is, this vector is a shared resource for my functions.
In this case, I don't know the correct approach to secure the share so that every thread can use it.
// This is class Worker
class Worker {
private:
std::thread check_oo_thread_{}; // thread #1
std::thread check_db_thread_{}; // thread #2
void check_oo(); // func #1
void check_db(); // func #2
std::vector<std::unique_ptr<database>> databases_list_; // This is a shared resource
bool is_work_{ false };
}
// Here I am running two threads
void Worker::start() {
is_work_ = true;
check_oo_thread_ = std::thread(&Worker::check_oo, this);
check_db_thread_ = std::thread(&Worker::check_db, this);
}
And here are two functions that use the databases_list_ vector in different threads. The difference is that these functions get different data from the database - it depends on the command_type parameter
// In this function, each database object is called in turn
void Worker::check_oo() {
while (is_work_) {
for (auto& db : databases_list_) {
auto db_cfg = db->get_cfg();
data_handler_.set_database(db_cfg.db_server);
auto db_respond = db->send_command(receive_cmd, command_type::get_data);
}
std::this_thread::sleep_for(std::chrono::seconds(processing_period_1));
}
}
// In this function, each database object is called in turn
void Worker::check_db() {
while (is_work_) {
for (auto& db : databases_list_) {
auto db_cfg = db->get_cfg();
data_handler_.set_database(db_cfg.db_server);
auto db_respond = db->send_command(receive_cmd, command_type::get_stat);
}
std::this_thread::sleep_for(std::chrono::seconds(processing_period_2));
}
}
This is a tricky case for me and I can't figure out how to get two threads to work on the shared resource databases_list_.
You can lock a std::mutex every time you want to access the shared resource. You also access is_work_ from multiple threads and I therefore suggest making that std::atomic<bool> is_work_ instead.
#include <atomic>
#include <mutex>
class Worker {
private:
std::thread check_oo_thread_{}; // thread #1
std::thread check_db_thread_{}; // thread #2
mutable std::mutex m_mtx; // use this to sync actions on your resource
std::atomic<bool> is_work_{}; // now atomic
void check_oo() {
while(is_work_) {
{
std::lock_guard<std::mutex> lock{m_mtx}; // locks the mutex
// use databases_list_ here
} // mutex is unlocked here
std::this_thread::sleep_for(...);
}
}
void check_db() {
while(is_work_) {
{
std::lock_guard<std::mutex> lock{m_mtx};
// use databases_list_ here
}
std::this_thread::sleep_for(...);
}
}
std::vector<std::unique_ptr<database>> databases_list_;
};
I have two functions foo and bar that should be mutually exclusive since they operate on the same data. However foo duplicates a lot of code from bar, so I would like to refactor foo to make a call to bar.
This is a problem because then I can't use a single mutex for both functions, because then foo would deadlock when it calls bar. So rather than "mutually exclusive" I only want "mutually exclusive from different threads".
Is there a pattern for implementing this? I'm using C++ and I'm okay with C++14/boost if I need something like shared_mutex.
Define a private "unlocked" function and use that from both foo and bar:
void bar_unlocked()
{
// assert that mx_ is locked
// real work
}
void bar()
{
std::lock_guard<std::mutex> lock(mx_);
bar_unlocked();
}
void foo()
{
std::lock_guard<std::mutex> lock(mx_);
// stuff
bar_unlocked();
// more stuff
}
another way - this has the advantage that you can prove that the lock has been taken:
void bar_impl(std::unique_lock<std::mutex> lock)
{
assert(lock.owns_lock());
// real work
}
void bar()
{
bar_impl(std::unique_lock<std::mutex>(mx_));
}
void foo()
{
// stuff
bar_impl(std::unique_lock<std::mutex>(mx_));
// more stuff
}
Rationale:
std::mutex is not (mandated by the standard to be) moveable, but a std::unique_lock<std::mutex> is. For this reason, we can move a lock into a callee and return it back to a caller (if necessary).
This allows us to prove ownership of the lock at every stage of a call chain.
In addition, once the optimiser gets involved, it's likely that all the lock-moving will be optimised away. This gives us the best of both worlds - provable ownership and maximal performance.
A more complete example:
#include <mutex>
#include <cassert>
#include <functional>
struct actor
{
//
// public interface
//
// perform a simple synchronous action
void simple_action()
{
impl_simple_action(take_lock());
}
/// perform an action either now or asynchronously in the future
/// hander() is called when the action is complete
/// handler is a latch - i.e. it will be called exactly once
/// #pre an existing handler must not be pending
void complex_action(std::function<void()> handler)
{
impl_complex_action(take_lock(), std::move(handler));
}
private:
//
// private external interface (for callbacks)
//
void my_callback()
{
auto lock = take_lock();
assert(!_condition_met);
_condition_met = true;
impl_condition_met(std::move(lock));
}
// private interface
using mutex_type = std::mutex;
using lock_type = std::unique_lock<mutex_type>;
void impl_simple_action(const lock_type& lock)
{
// assert preconditions
assert(lock.owns_lock());
// actions here
}
void impl_complex_action(lock_type my_lock, std::function<void()> handler)
{
_handler = std::move(handler);
if (_condition_met)
{
return impl_condition_met(std::move(my_lock));
}
else {
// initiate some action that will result in my_callback() being called
// some time later
}
}
void impl_condition_met(lock_type lock)
{
assert(lock.owns_lock());
assert(_condition_met);
if(_handler)
{
_condition_met = false;
auto copy = std::move(_handler);
// unlock here because the callback may call back into our public interface
lock.unlock();
copy();
}
}
auto take_lock() const -> lock_type
{
return lock_type(_mutex);
}
mutable mutex_type _mutex;
std::function<void()> _handler = {};
bool _condition_met = false;
};
void act(actor& a)
{
a.complex_action([&a]{
// other stuff...
// note: calling another public interface function of a
// during a handler initiated by a
// the unlock() in impl_condition_met() makes this safe.
a.simple_action();
});
}
I want a member std::future<void> to continuously call a function inside a loop until the parent object is destroyed.
My current solution involves wrapping the future in a class with a boolean flag and setting the flag to false on destruction.
class Wrapper
{
std::future<void> fut;
bool wrapperAlive{true};
public:
Wrapper() : fut{std::async(std::launch::async, [this]
{
while(wrapperAlive) doSomething();
})} { }
~Wrapper()
{
wrapperAlive = false;
}
};
Is there a more idiomatic way of doing this?
This is a data-race free version of your code:
class Wrapper {
std::atomic<bool> wrapperAlive{true}; // construct flag first!
std::future<void> fut;
public:
Wrapper() :
fut{std::async(std::launch::async, [this]
{
while(wrapperAlive)
doSomething();
}
)}
{}
~Wrapper() {
wrapperAlive = false;
fut.get(); // block, so it sees wrapperAlive before it is destroyed.
}
};
the next thing I'd do is write:
template<class F>
struct repeat_async_t {
F f;
// ...
};
using repeat_async = repeat_async_t<std::function<void()>>;
template<class F>
repeat_async_t<std::decay_t<F>> make_repeat_async(F&&f){
return {std::forward<F>(f)};
}
which takes a task to repeat forever, and bundle it up in there, rather than mixing the flow logic with what is executed logic.
At this point, we will probably want to add in an abort method.
Finally, it is very rarely a good idea to busy-loop a thread. So we'd add in some kind of wait-for-more-data-to-consume system.
And it ends up looking a lot different than your code.
For example I've an EventGenerator class that call IEventHandler::onEvent for all registered event handlers:
class IEventHandler {
public: virtual void onEvent(...) = 0;
};
class EventGenerator {
private:
std::vector<IEventHandler*> _handlers;
std::mutex _mutex; // [1]
public:
void AddHandler(IEventHandler* handler) {
std::lock_guard<std::mutex> lck(_mutex); // [2]
_handlers.push_back(handler);
}
void RemoveHanler(IEventHandler* handler) {
std::lock_guard<std::mutex> lck(_mutex); // [3]
// remove from "_handlers"
}
private:
void threadMainTask() {
while(true) {
// Do some work ...
// Post event to all registered handlers
{
std::lock_guard<std::mutex> lck(_mutex); // [4]
for(auto& h : _handlers) { h->onEvent(...); )
}
// Do some work ...
}
}
The code should be thread safe in the following manner:
one thread is executing the EventGenerator::threadMainTask
many threads might access EventGenerator::AddHandler and EventGenerator::RemoveHandler APIs.
To support this, I have the following synchonization (see comment in the code):
[1] is the mutex that protects the vector _handlers from multiple thread access.
[2] and [3] are protect adding or removing handlers simultaneously.
[4] is preventing from changing the vector while the main thread is posting events.
This code works until... If for some reason, during the execution of IEventHandler::onEvent(...) the code is trying to call EventManager::RemoveHandler or EventManager::AddHandler. The result is runtime exception.
What is the best approach to handle registration of the event handlers and executing the event handler callback in the thread safe manner?
>> UPDATE <<
So based on the inputs, I've updated to the following design:
class IEventHandler {
public: virtual void onEvent(...) = 0;
};
class EventDelegate {
private:
IEventHandler* _handler;
std::atomic<bool> _cancelled;
public:
EventDelegate(IEventHandler* h) : _handler(h), _cancelled(false) {};
void Cancel() { _cancelled = true; }
void Invoke(...) { if (!_cancelled) _handler->onEvent(...); }
}
class EventGenerator {
private:
std::vector<std::shared_ptr<EventDelegate>> _handlers;
std::mutex _mutex;
public:
void AddHandler(std::shared_ptr<EventDelegate> handler) {
std::lock_guard<std::mutex> lck(_mutex);
_handlers.push_back(handler);
}
void RemoveHanler(std::shared_ptr<EventDelegate> handler) {
std::lock_guard<std::mutex> lck(_mutex);
// remove from "_handlers"
}
private:
void threadMainTask() {
while(true) {
// Do some work ...
std::vector<std::shared_ptr<EventDelegate>> handlers_copy;
{
std::lock_guard<std::mutex> lck(_mutex);
handlers_copy = _handlers;
}
for(auto& h : handlers_copy) { h->Invoke(...); )
// Do some work ...
}
}
As you can see, there is additional class EventDelegate that have two purposes:
hold the event callback
enable to cancel the callback
In the threadMainTask, I'm using a local copy of the std::vector<std::shared_ptr<EventDelegate>> and I'm releasing the lock before invoking the callbacks. This approach solves an issue when during the IEventHandler::onEvent(...) the EventGenerator::{AddHandler,RemoveHanler} is called.
Any thoughts about the new design?
Copy-on-Write vector implemented on atomic swap of shared_ptr's (in assumptions callback registration is occurring far less frequently than events the callbacks are notified about):
using callback_t = std::shared_ptr<std::function<void(event_t const&)> >;
using callbacks_t = std::shared_ptr<std::vector<callback_t> >;
callbacks_t callbacks_;
mutex_t mutex_; // a mutex of your choice
void register(callback_t cb)
{
// the mutex is to serialize concurrent callbacks registrations
// this is not always necessary, as depending on the application
// architecture, single writer may be enforced by design
scoped_lock lock(mutex_);
auto callbacks = atomic_load(&callbacks_);
auto new_callbacks = std::make_shared< std::vector<callback_t> >();
new_callbacks->reserve(callbacks->size() + 1);
*new_callbacks = callbacks;
new_callbacks->push_back(std::move(cb));
atomic_store(&callbacks_, new_callbacks);
}
void invoke(event_t const& evt)
{
auto callbacks = atomic_load(&callbacks_);
// many people wrap each callback invocation into a try-catch
// and de-register on exception
for(auto& cb: *callbacks) (*cb)(evt);
}
Specifically on the subject of asynchronous behavior when callback is executed while being de-registered, well here the best approach to take is remember of the Separation of Concerns principle.
The callback should not be able to die until it has been executed. This is achieved via another classic trick called "extra level of indirection". Namely, instead of registering user provided callback one would wrap it to something like the below and callback de-registration apart from updating the vector will call the below defined discharge() method on the callback wrapper and will even notify the caller of de-registration method of whether the callback execution finished successfully.
template <class CB> struct cb_wrapper
{
mutable std::atomic<bool> done_;
CB cb_;
cb_wrapper(CB&& cb): cb(std::move(cb_)) {}
bool discharge()
{
bool not_done = false;
return done_.compare_exchange_strong(not_done, true);
}
void operator()(event_t const&)
{
if (discharge())
{
cb();
}
}
};
I can't see a right thing here. From your update I can see a problem: you are not synchronizing the invoke method with callback removal. There's an atomic but it's not enough. Example: just after this line of code:
if (!_cancelled)
Another thread calls the remove method. What can happen is that the onEvent() is called anyway, even if the removed method has removed the callback from the list and returned the result, there's nothing to keep synchronized this execution flow. Same problem for the answer of #bobah.
I am trying to design a multithreaded event system in C++. In it, the objects may be located in different threads and every object should be able to queue events for other threads. Each thread has its own event queue and event dispatcher, as well as an event loop. It should be possible to change the thread affinity of the objects.
Let's say we have two threads: A and B, and an object myobj, which belongs to B. Obviously, A needs a pointer to myobj in order to be able to send events to it. A doesn't have any pointer to B, but it needs some way to get a reference to it in order to be able to lock the event queue and add the event to it.
I could store a pointer to B in myobj, but then I obviously need to protect myobj. If I place a mutex in myobj, myobj could be destructed while the mutex is being locked, thus causing a segmentation fault.
I could also use a global table where I associate each object with its corresponding thread. However, this would consume a lot of memory and cause any thread that wants to send an event to block until A has finish
ed.
What is the most efficient safe strategy to implement this? Is there perhaps some kind of design pattern for this?
Thanks in advance.
I've implemented a thread wrapper base class ThreadEventComponent for sending and processing events between instances of itself. Each ThreadEventComponent has it's own event queue that is automatically locked internally whenever used. The events themselves are negotiated by a static map of type map<EventKey, vector<ThreadEventComponent*>> that is also automatically locked whenever used. As you can see, multiple ThreadEventComponent derived instances can subscribe to the same event. Each event sent with SendEvent(Event*) is copied per instance to insure that multiple threads aren't fighting over the same data held within the event.
Admittedly, this is not the most efficient strategy, opposed to sharing memory. There are optimizations to be made regarding the addEvent(Event&)method. With drawbacks aside, it does work well for configuring a thread to do some operation outside of the main thread.
Both MainLoop() and ProcessEvent(Event*) are virtual functions to be implemented by the derived class. ProcessEvent(Event*) is called whenever an event is available in the queue. After that, MainLoop() is called regardless of the event queue state. MainLoop() is where you should tell your thread to sleep and where any other operations such as file reading/writing or network reading/writing should go.
The following code is something I've been working on for my own person use to get my head wrapped around threading in C++. This code has never been reviewed, so I'd love to hear any suggestions you have. I am aware of two elements that are less than desirable in this code sample. 1) I'm using new at run-time, the drawback being that finding memory takes time, but this can be mitigated by creating a memory buffer to construct new events over in the ThreadEventComponent base class. 2)Event casting to TEvent<T> can cause run-time errors if not implemented correctly in ProcessEvent. I'm not sure what the best solution for this is.
Note: I have EventKey implemented as a string, but you can change it to whatever type you wish as long as it has a default value along with the equality and assignment operators available.
Event.h
#include <string>
using namespace std;
typedef string EventKey;
class Event
{
public:
Event()
: mKey()
{
}
Event(EventKey key)
: mKey(key)
{
}
Event(const Event& e)
: mKey(e.mKey)
{
}
virtual ~Event()
{
}
EventKey GetKey()
{
return mKey;
}
protected:
EventKey mKey;
};
template<class T>
class TEvent : public Event
{
public:
TEvent()
: Event()
{
}
TEvent(EventKey type, T& object)
: Event(type), mObject(object)
{
}
TEvent(const TEvent<T>& e)
: Event(e.mKey), mObject(e.mObject)
{
}
virtual ~TEvent()
{
}
T& GetObject()
{
return mObject;
}
private:
T mObject;
};
ThreadEventComponent.h
#include "Event.h"
#include <thread>
#include <atomic>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <mutex>
#include <assert.h>
class ThreadEventComponent
{
public:
ThreadEventComponent();
~ThreadEventComponent();
void Start(bool detached = false);
void Stop();
void ForceStop();
void WaitToFinish();
virtual void Init() = 0;
virtual void MainLoop() = 0;
virtual void ProcessEvent(Event* incoming) = 0;
template<class T>
void SendEvent(TEvent<T>& e)
{
sEventListLocker.lock();
EventKey key = e.GetKey();
for (unsigned int i = 0; i < sEventList[key].size(); i++)
{
assert(sEventList[key][i] != nullptr);
sEventList[key][i]->addEvent<T>(e);
}
sEventListLocker.unlock();
}
void SendEvent(Event& e);
void Subscribe(EventKey key);
void Unsubscribe(EventKey key);
protected:
template<class T>
void addEvent(TEvent<T>& e)
{
mQueueLocker.lock();
// The event gets copied per thread
mEventQueue.push(new TEvent<T>(e));
mQueueLocker.unlock();
}
void addEvent(Event& e);
thread mThread;
atomic<bool> mShouldExit;
private:
void threadLoop();
queue<Event*> mEventQueue;
mutex mQueueLocker;
typedef map<EventKey, vector<ThreadEventComponent*>> EventMap;
static EventMap sEventList;
static mutex sEventListLocker;
};
ThreadEventComponent.cpp
#include "ThreadEventComponent.h"
ThreadEventComponent::EventMap ThreadEventComponent::sEventList = ThreadEventComponent::EventMap();
std::mutex ThreadEventComponent::sEventListLocker;
ThreadEventComponent::ThreadEventComponent()
{
mShouldExit = false;
}
ThreadEventComponent::~ThreadEventComponent()
{
}
void ThreadEventComponent::Start(bool detached)
{
mShouldExit = false;
mThread = thread(&ThreadEventComponent::threadLoop, this);
if (detached)
mThread.detach();
}
void ThreadEventComponent::Stop()
{
mShouldExit = true;
}
void ThreadEventComponent::ForceStop()
{
mQueueLocker.lock();
while (!mEventQueue.empty())
{
delete mEventQueue.front();
mEventQueue.pop();
}
mQueueLocker.unlock();
mShouldExit = true;
}
void ThreadEventComponent::WaitToFinish()
{
if(mThread.joinable())
mThread.join();
}
void ThreadEventComponent::SendEvent(Event& e)
{
sEventListLocker.lock();
EventKey key = e.GetKey();
for (unsigned int i = 0; i < sEventList[key].size(); i++)
{
assert(sEventList[key][i] != nullptr);
sEventList[key][i]->addEvent(e);
}
sEventListLocker.unlock();
}
void ThreadEventComponent::Subscribe(EventKey key)
{
sEventListLocker.lock();
if (find(sEventList[key].begin(), sEventList[key].end(), this) == sEventList[key].end())
{
sEventList[key].push_back(this);
}
sEventListLocker.unlock();
}
void ThreadEventComponent::Unsubscribe(EventKey key)
{
sEventListLocker.lock();
// Finds event listener of correct type
EventMap::iterator mapIt = sEventList.find(key);
assert(mapIt != sEventList.end());
// Finds the pointer to itself
std::vector<ThreadEventComponent*>::iterator elIt =
std::find(mapIt->second.begin(), mapIt->second.end(), this);
assert(elIt != mapIt->second.end());
// Removes it from the event list
mapIt->second.erase(elIt);
sEventListLocker.unlock();
}
void ThreadEventComponent::addEvent(Event& e)
{
mQueueLocker.lock();
// The event gets copied per thread
mEventQueue.push(new Event(e));
mQueueLocker.unlock();
}
void ThreadEventComponent::threadLoop()
{
Init();
bool shouldExit = false;
while (!shouldExit)
{
if (mQueueLocker.try_lock())
{
if (mEventQueue.empty())
{
mQueueLocker.unlock();
if(mShouldExit)
shouldExit = true;
}
else
{
Event* e = mEventQueue.front();
mEventQueue.pop();
mQueueLocker.unlock();
ProcessEvent(e);
delete e;
}
}
MainLoop();
}
}
Example Class - A.h
#include "ThreadEventComponent.h"
class A : public ThreadEventComponent
{
public:
A() : ThreadEventComponent()
{
}
void Init()
{
Subscribe("a stop");
Subscribe("a");
}
void MainLoop()
{
this_thread::sleep_for(50ms);
}
void ProcessEvent(Event* incoming)
{
if (incoming->GetKey() == "a")
{
auto e = static_cast<TEvent<vector<int>>*>(incoming);
mData = e->GetObject();
for (unsigned int i = 0; i < mData.size(); i++)
{
mData[i] = sqrt(mData[i]);
}
SendEvent(TEvent<vector<int>>("a done", mData));
}
else if(incoming->GetKey() == "a stop")
{
StopWhenDone();
}
}
private:
vector<int> mData;
};
Example Class - B.h
#include "ThreadEventComponent.h"
int compare(const void * a, const void * b)
{
return (*(int*)a - *(int*)b);
}
class B : public ThreadEventComponent
{
public:
B() : ThreadEventComponent()
{
}
void Init()
{
Subscribe("b stop");
Subscribe("b");
}
void MainLoop()
{
this_thread::sleep_for(50ms);
}
void ProcessEvent(Event* incoming)
{
if (incoming->GetKey() == "b")
{
auto e = static_cast<TEvent<vector<int>>*>(incoming);
mData = e->GetObject();
qsort(&mData[0], mData.size(), sizeof(int), compare);
SendEvent(TEvent<vector<int>>("b done", mData));
}
else if (incoming->GetKey() == "b stop")
{
StopWhenDone();
}
}
private:
vector<int> mData;
};
Test Example - main.cpp
#include <iostream>
#include <random>
#include "A.h"
#include "B.h"
class Master : public ThreadEventComponent
{
public:
Master() : ThreadEventComponent()
{
}
void Init()
{
Subscribe("a done");
Subscribe("b done");
}
void MainLoop()
{
this_thread::sleep_for(50ms);
}
void ProcessEvent(Event* incoming)
{
if (incoming->GetKey() == "a done")
{
TEvent<vector<int>>* e = static_cast<TEvent<vector<int>>*>(incoming);
cout << "A finished" << endl;
mDataSetA = e->GetObject();
for (unsigned int i = 0; i < mDataSetA.size(); i++)
{
cout << mDataSetA[i] << " ";
}
cout << endl << endl;
}
else if (incoming->GetKey() == "b done")
{
TEvent<vector<int>>* e = static_cast<TEvent<vector<int>>*>(incoming);
cout << "B finished" << endl;
mDataSetB = e->GetObject();
for (unsigned int i = 0; i < mDataSetB.size(); i++)
{
cout << mDataSetB[i] << " ";
}
cout << endl << endl;
}
}
private:
vector<int> mDataSetA;
vector<int> mDataSetB;
};
int main()
{
srand(time(0));
A a;
B b;
a.Start();
b.Start();
vector<int> data;
for (int i = 0; i < 100; i++)
{
data.push_back(rand() % 100);
}
Master master;
master.Start();
master.SendEvent(TEvent<vector<int>>("a", data));
master.SendEvent(TEvent<vector<int>>("b", data));
master.SendEvent(TEvent<vector<int>>("a", data));
master.SendEvent(TEvent<vector<int>>("b", data));
master.SendEvent(Event("a stop"));
master.SendEvent(Event("b stop"));
a.WaitToFinish();
b.WaitToFinish();
// cin.get();
master.StopWhenDone();
master.WaitToFinish();
return EXIT_SUCCESS;
}
I have not used it myself, but Boost.Signals2 claims to be thread-safe.
The primary motivation for Boost.Signals2 is to provide a version of the original Boost.Signals library which can be used safely in a multi-threaded environment.
Of course, using this would make your project depend on boost, which might not be in your interest.
[edit] It seems slots are executed in the emitting thread (no queue), so this might not be what you had in mind after all.
I'd consider making the thread part of classes to encapsulate them. That way you can easily design your interfaces around the thread loops (provided as member functions of these classes) and have defined entry points to send data to the thread loop (e.g. using a std::queue protected with a mutex).
I don't know if this is a designated, well known design pattern, but that's what I'm using for my all day productive code at work, and I (and my colleagues) feel and experience pretty good with it.
I'll try to give you a point:
class A {
public:
A() {}
bool start();
bool stop();
bool terminate() const;
void terminate(bool value);
int data() const;
void data(int value);
private:
std::thread thread_;
void threadLoop();
bool terminate_;
mutable std::mutex internalDataGuard_;
int data_;
};
bool A::start() {
thread_ = std::thread(std::bind(this,threadLoop));
return true;
}
bool A::stop() {
terminate(true);
thread_.join();
return true;
}
bool A::terminate() const {
std::lock_guard<std::mutex> lock(internalDataGuard_);
return terminate_;
}
void A::terminate(bool value) {
std::lock_guard<std::mutex> lock(internalDataGuard_);
terminate_ = value;
}
int A::data() const {
std::lock_guard<std::mutex> lock(internalDataGuard_);
return data_;
}
void A::data(int value) {
std::lock_guard<std::mutex> lock(internalDataGuard_);
data_ = value;
// Notify thread loop about data changes
}
void A::threadLoop() {
while(!terminate())
{
// Wait (blocking) for data changes
}
}
To setup signalling of data changes there are several choices and (OS) constraints:
The simplest thing you could use to wake up the thread loop to process changed/new data is a semaphore. In c++11 the nearest approx for a semaphore is a condition variable. Advanced versions of the pthreads API also provide condition variable support. Anyway since only one thread should be waiting there, and no kind of event broadcasing is necessary, it should be easy to implement with simple locking mechanisms.
If you have the choice to use an advanced OS, you might prefer implementing event signalling using s.th. like poll(), which provides lock-free implementation at the user space.
Some frameworks like boost, Qt, Platinum C++, and others also support event handling by signal/slot abstractions, you might have a look at their documentation and implementation to get a grip what's necessary/state of the art.
Obviously, A needs a pointer to myobj in order to be able to send
events to it.
I question the above assumption -- To me, allowing thread A to have a pointer to an object that is controlled/owned/accessed by thread B is kind of asking for trouble... in particular, some code running in thread A might be tempted later on to use that pointer to directly call methods on myobj, causing race conditions and discord; or B might delete myobj, at which point A is holding a dangling-pointer and is thereby in a precarious state.
If I was designing the system, I would try to do it in such a way that cross-thread messaging was done without requiring pointers-to-objects-in-other-threads, for the reasons you mention -- they are unsafe, in particular such a pointer might become a dangling-pointer at any time.
So then the question becomes, how do I send a message to an object in another thread, if I don't have a pointer to that object?
One way would be to give each object a unique ID by which it can be specified. This ID could be an integer (either hard-coded or dynamically assigned using an atomic counter or similar), or perhaps a short string if you wanted it to be more easily human-readable.
Then instead of the code in thread A sending the message directly to myobj, it would send a message to thread B, and the message would include a field indicating the ID of the object that is intended to receive the message.
When thread B's event loop receives the message, it would use the included ID value to look up the appropriate object (using an efficient key-value lookup mechanism such as std::unordered_map) and call the appropriate method on that object. If the object had already been destroyed, then the key-value lookup would fail (because you'd have a mechanism to make sure that the object removed itself from its thread's object-map as part of its destructor), and thus trying to send a message to a destroyed-object would fail cleanly (as opposed to invoking undefined behavior).
Note that this approach does mean that thread A's code has to know which thread myobj is owned by, in order to know which thread to send the message to. Typically thread A would need to know that anyway, but if you're going for a design that abstracts away even the knowledge about which thread a given object is running in, you could include an owner-thread-ID as part of the object-ID, so that your postMessage() method could examine the destination-object-ID to figure out which thread to send the message to.