I implemented a ThreadPool to test my knowledge of C++ concurrency. However, when I run the following code, it does not proceed and my mac becomes extremely slow and eventually does not respond—I check the monitor later and find the reason is that the kernel_task launches several clang processes and each runs nearly 100% CPU. I've carefully gone through the code several times, but still unable to locate the problem.
Here's the test code for ThreadPool. When I run this code, there is nothing printed on the terminal. Worse still, even if I cancel the process(via contrl+c), kernel_task creates several clang later and my computer crashes.
// test code for ThreadPool
#include <iostream>
#include <functional>
#include <future>
#include "thread_pool.hpp"
int task() {
static std::atomic<int> i = 1;
std::cout << i.fetch_add(1, std::memory_order_relaxed) << "task\n";
return i.load(std::memory_order_relaxed);
}
int main() {
ThreadPool<int()> thread_pool(1);
auto f1 = thread_pool.submit(task, false);
std::cout << "hello" << '\n';
auto f2 = thread_pool.submit(task, false);
std::cout << f1.get() << '\n';
std::cout << f2.get() << '\n';
}
Here's the definition of ThreadPool.
// thread_pool.hpp
#include <atomic>
#include <algorithm>
#include <chrono>
#include <functional>
#include <future>
#include <memory>
#include <queue>
#include <thread>
#include "queue.hpp"
template<typename Func>
class ThreadPool {
public:
ThreadPool(std::size_t=std::thread::hardware_concurrency()); // should I minus one here for the main thread?
~ThreadPool();
template<typename... Args,
typename ReturnType=typename std::result_of<std::decay_t<Func>(std::decay_t<Args>...)>::type>
std::future<ReturnType> submit(Func f, bool local=true);
private:
void worker_thread();
void run_task();
using LocalThreadType = std::queue<std::packaged_task<Func>>;
static thread_local LocalThreadType local_queue; // local queue, not used for now
using ThreadSafeQueue = LockBasedQueue<std::packaged_task<int()>,
std::list<std::packaged_task<int()>>>;
std::shared_ptr<ThreadSafeQueue> shared_queue;
std::atomic_bool done;
std::vector<std::thread> threads;
};
template<typename Func>
ThreadPool<Func>::ThreadPool(std::size_t n): done(false) {
threads.emplace_back(&ThreadPool::worker_thread, this);
}
template<typename Func>
ThreadPool<Func>::~ThreadPool() {
done.store(true, std::memory_order_relaxed);
for (auto& t: threads)
t.join();
}
template<typename Func>
template<typename...Args,
typename ReturnType>
std::future<ReturnType> ThreadPool<Func>::submit(Func f, bool local) {
auto result = local? post_task(f, local_queue):
post_task(f, *shared_queue);
return result;
}
template<typename Func>
void ThreadPool<Func>::run_task() {
if (!local_queue.empty()) {
auto task = std::move(local_queue.front());
local_queue.pop();
task();
}
else {
std::packaged_task<Func> task;
auto flag = shared_queue->try_pop(task);
if (flag)
task();
else {
using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);
}
}
}
template<typename Func>
void ThreadPool<Func>::worker_thread() {
while (!done.load(std::memory_order_relaxed)) {
run_task();
}
}
template<typename Func>
thread_local typename ThreadPool<Func>::LocalThreadType ThreadPool<Func>::local_queue = {};
Here's the definition of post_task and LockBasedQueue, which have passed the test code in the next code block.
// queue.hpp
#include <mutex>
#include <list>
#include <deque>
#include <queue>
#include <memory>
#include <future>
#include <condition_variable>
template<typename T, typename Container>
class LockBasedQueue; // forward declaration
template<typename Func, typename... Args,
typename ReturnType=typename std::result_of<std::decay_t<Func>(std::decay_t<Args>...)>::type,
typename Container=std::list<Func>,
typename ThreadQueue=LockBasedQueue<std::packaged_task<ReturnType(Args...)>, Container>>
std::future<ReturnType> post_task(Func f, ThreadQueue& task_queue) {
std::packaged_task<ReturnType(Args...)> task(f);
std::future res = task.get_future();
task_queue.push(std::move(task)); // packaged_task is not copyable
return res;
}
// the general template is omitted and it's not needed in this context
template<typename T>
class LockBasedQueue<T, std::list<T>> {
public:
// constructors
LockBasedQueue(): head(std::make_unique<Node>()), tail(head.get()) {}
LockBasedQueue(LockBasedQueue&&);
// assignments
LockBasedQueue& operator=(LockBasedQueue&&);
// general purpose operations
void swap(LockBasedQueue&);
bool empty() const;
std::size_t size() const;
// queue operations
void push(const T&);
void push(T&&);
template <typename... Args>
void emplace(Args&&... args);
T pop();
bool try_pop(T&);
// delete front() and back(), these functions may waste notifications. To enable these function, one should replace notify_one() with notify_all() in push() and emplace()
T& front() = delete;
const T& front() const = delete;
T& back() = delete;
const T& back() const = delete;
private:
struct Node {
std::unique_ptr<T> data; // data is a pointer as it may be empty
std::unique_ptr<Node> next;
};
Node* get_tail() {
std::lock_guard l(tail_mutex);
return tail;
}
Node* get_head() {
std::lock_guard l(head_mutex);
return head.get();
}
std::unique_lock<std::mutex> get_head_lock() {
std::unique_lock l(head_mutex);
data_cond.wait(l, [this] { return head.get() != get_tail(); });
return l;
}
T pop_data() {
auto data = std::move(*head->data);
head = std::move(head->next); // we move head to the next so that the tail is always valid
return std::move(data);
}
std::unique_ptr<Node> head;
std::mutex head_mutex;
Node* tail;
std::mutex tail_mutex;
std::condition_variable data_cond;
};
template<typename T>
LockBasedQueue<T, std::list<T>>::LockBasedQueue(
LockBasedQueue<T, std::list<T>>&& other) {
{
std::scoped_lock l(head_mutex, other.head_mutex);
head(std::move(other.data_queue));
}
{
std::lock_guard l(tail_mutex);
tail = head.get();
}
{
std::lock_guard l(other.tail_mutex);
other.tail = nullptr;
}
}
template<typename T>
LockBasedQueue<T, std::list<T>>&
LockBasedQueue<T, std::list<T>>::operator=(
LockBasedQueue<T, std::list<T>>&& rhs) {
{
std::scoped_lock l(head_mutex, rhs.head_mutex);
head(std::move(rhs.data_queue));
}
{
std::lock_guard l(tail_mutex);
tail = head.get();
}
{
std::lock_guard l(rhs.tail_mutex);
rhs.tail = nullptr;
}
}
template<typename T>
void LockBasedQueue<T, std::list<T>>::swap(
LockBasedQueue<T, std::list<T>>& other) {
{
std::scoped_lock l(head_mutex, other.head_mutex);
head(std::move(other.data_queue));
}
{
std::lock_guard l(tail_mutex);
tail = head.get();
}
{
std::lock_guard l(other.tail_mutex);
other.tail = other.head.get();
}
}
template<typename T>
inline bool LockBasedQueue<T, std::list<T>>::empty() const {
return get_head() == get_tail();
}
template<typename T>
std::size_t LockBasedQueue<T, std::list<T>>::size() const {
int n = 0;
std::lock_guard l(tail_mutex); // do not use get_tail() here to avoid race condition
for (auto p = get_head(); p != tail; p = p->next.get())
++n;
return n;
}
template<typename T>
void LockBasedQueue<T, std::list<T>>::push(const T& data) {
push(T(data));
}
template<typename T>
void LockBasedQueue<T, std::list<T>>::push(T&& data) {
{
auto p = std::make_unique<Node>();
std::lock_guard l(tail_mutex);
tail->data = std::make_unique<T>(std::move(data)); // we add data to the current tail, this allows us to move head to the next when popping
tail->next = std::move(p);
tail = tail->next.get();
}
data_cond.notify_one();
}
template<typename T>
template<typename...Args>
void LockBasedQueue<T, std::list<T>>::emplace(Args&&... args) {
{
auto p = std::make_unique<Node>();
std::lock_guard l(tail_mutex);
tail->data = std::make_unique<T>(std::forward<Args>(args)...);
tail->next = std::move(p);
tail = tail->next.get();
}
data_cond.notify_one();
}
template<typename T>
T LockBasedQueue<T, std::list<T>>::pop() {
auto l(get_head_lock());
return pop_data();
}
template<typename T>
bool LockBasedQueue<T, std::list<T>>::try_pop(T& data) {
std::lock_guard l(head_mutex);
if (head.get() == get_tail())
return false;
data = pop_data();
return true;
}
Here's the code I used to test LockBasedQueue and post_task. The following test code works without any problem.
// test code for LockBasedQueue and post_task
#include <iostream>
#include <functional>
#include <future>
#include "queue.hpp"
LockBasedQueue<std::packaged_task<int()>, std::list<std::packaged_task<int()>>> task_queue; // thread safe queue, which handles locks inside
void task_execution_thread() {
bool x = true;
while (x) { // for debugging purpose, we only execute this loop once
auto task = task_queue.pop(); // Returns the front task and removes it from queue. Waits if task_queue is empty
task(); // execute task
x = false;
}
}
int task() {
static std::atomic<int> i = 1;
std::cout << i.fetch_add(1, std::memory_order_relaxed) << "task\n";
return i.load(std::memory_order_relaxed);
}
int main() {
std::thread t1(task_execution_thread);
std::thread t2(task_execution_thread);
auto f1 = post_task(task, task_queue);
auto f2 = post_task(task, task_queue);
std::cout << "f1: " << f1.get() << '\n';
std::cout << "f2: " << f2.get() << '\n';
t1.join();
t2.join();
}
I test the code using g++ -std=c++2a on the MacOS 11.2.3.
shared_queue is default initialised therefore calling methods on it is undefined behaviour. Initialising it in the constructor of ThreadPool:
ThreadPool<Func>::ThreadPool(std::size_t n) : done(false), shared_queue(std::make_shared<ThreadSafeQueue>()) {
makes your code work: https://godbolt.org/z/P9G1T5
Related
As stated in an answer to this question, std::invoke handles calling not just of simple functions but also other callable types.
Unfortunately I am currently constrained to C++14 - so does anyone know of a C++14 compatible alternative?
My motivation:
My actual problem is that I am trying to write a simple wrapper for std::thread where I want to wrap the call of the passed function f with info like isRunning, which will be set to true before calling f(); and false afterwards.
One possible way I found, was to pass a member-method bound via std::bind(&Class::method, classInstance) - this however breaks the API, as this is something the user of my Wrapper class would have to do.
In case anyone is interested, here is my code:
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
class ThreadPeriodic : public std::thread
{
protected:
struct Settings
{
Settings(std::chrono::nanoseconds const periodDuration)
: isRunning(false)
, stopped(false)
, periodDuration(periodDuration)
{
}
volatile std::atomic<bool> isRunning;
std::mutex mutexStop;
std::condition_variable conditionVariableStop;
volatile std::atomic<bool> stopped;
std::chrono::nanoseconds const periodDuration;
};
std::shared_ptr<Settings> settings_;
template< class Function, class... Args >
class WrapperClass_
{
WrapperClass_() = delete;
public:
// https://stackoverflow.com/questions/34731367/how-to-pass-variadic-args-to-a-stdthread
static void wrapperMethod(std::shared_ptr<Settings> settings,
typename std::decay<Function>::type&& f,
typename std::decay<Args>::type&&... args)
{
settings->isRunning = true;
std::chrono::steady_clock::time_point nextPeriod = std::chrono::steady_clock::now();
bool stopped = settings->stopped.load();
while (!stopped)
{
try
{
f(std::forward<Args>(args)...);
}
catch (...)
{
// allthough this should never happen...
settings->isRunning = false;
throw;
}
nextPeriod += settings->periodDuration;
std::unique_lock<std::mutex> lock(settings->mutexStop);
stopped = settings->conditionVariableStop.wait_until(lock, nextPeriod, [settings](){return settings->stopped;});
}
settings->isRunning = false;
}
};
public:
ThreadPeriodic() noexcept
{
}
ThreadPeriodic(ThreadPeriodic && other) noexcept
{
operator=(std::move(other));
}
template< class Function, class... Args >
explicit ThreadPeriodic(std::chrono::nanoseconds const periodDuration, Function&& f, Args&&... args)
: settings_(std::make_shared<Settings>(periodDuration))
{
std::thread::operator=(std::thread(ThreadPeriodic::WrapperClass_<Function, Args...>::wrapperMethod,
settings_,
std::forward<Function>(f),
std::forward<Args>(args)...));
}
template< class Function, class... Args >
explicit ThreadPeriodic(Function&& f, Args&&... args)
: ThreadPeriodic(std::chrono::nanoseconds(0), std::forward<Function>(f), std::forward<Args>(args)...)
{
}
template< class Rep, class Period, class Function, class... Args >
explicit ThreadPeriodic(std::chrono::duration<Rep, Period> const periodDuration, Function&& f, Args&&... args)
: ThreadPeriodic(std::chrono::duration_cast<std::chrono::nanoseconds>(periodDuration), std::forward<Function>(f), std::forward<Args>(args)...)
{
}
ThreadPeriodic( const ThreadPeriodic& ) = delete;
ThreadPeriodic& operator=( ThreadPeriodic&& other ) noexcept
{
std::thread::operator=(std::move(other));
settings_ = std::move(other.settings_);
return *this;
}
bool isRunning() const
{
std::shared_ptr<Settings> settings = settings_;
return static_cast<bool>(settings) ? settings->isRunning.load() : false;
}
bool isStarted() const
{
std::shared_ptr<Settings> settings = settings_;
return static_cast<bool>(settings) && !settings->stopped;
}
bool isStopped() const
{
std::shared_ptr<Settings> settings = settings_;
return static_cast<bool>(settings) ? settings->stopped.load() : true;
}
// If joinNow is false, join() shall be called later on manually, as to ensure
// the thread is actually joined as it should.
void stop(bool const joinNow = true)
{
std::shared_ptr<Settings> settings = settings_;
if (!static_cast<bool>(settings))
{
throw std::logic_error("ThreadPeriodic::stop: this instance does not represent a thread.");
}
else if (settings->stopped)
{
throw std::logic_error("ThreadPeriodic::stop: this instance is already stopped.");
}
else
{
{
std::unique_lock<std::mutex> lock(settings->mutexStop);
settings->stopped = true;
}
settings->conditionVariableStop.notify_all();
if (joinNow && joinable())
{
join();
}
}
}
};
And if anyone wants to test it, here is a basic program:
#include <iostream>
class TestClass
{
public:
explicit TestClass(int const start = 0)
: start(start)
{
}
void printNumber(int const step = 1)
{
static int number = start;
std::cout << number << std::endl;
number += step;
}
int start;
};
int main()
{
TestClass testInstance(0);
// ThreadPeriodic thread(std::chrono::milliseconds(500), &TestClass::printNumber, testInstance, 3);
ThreadPeriodic thread(std::chrono::milliseconds(500), std::bind(&TestClass::printNumber, testInstance, std::placeholders::_1), 3);
std::this_thread::sleep_for(std::chrono::seconds(2));
thread.stop();
return 0;
}
The topic says it. I don't understand why the std::queue (or in general: any queue) is not thread-safe by its nature, when there is no iterator involved as with other datastructures.
According to the common rule that
at least one thread is writing to ...
and another thread is reading from a shared resource
I should have gotten a conflict in the following example code:
#include "stdafx.h"
#include <queue>
#include <thread>
#include <iostream>
struct response
{
static int & getCount()
{
static int theCount = 0;
return theCount;
}
int id;
};
std::queue<response> queue;
// generate 100 response objects and push them into the queue
void produce()
{
for (int i = 0; i < 100; i++)
{
response r;
r.id = response::getCount()++;
queue.push(r);
std::cout << "produced: " << r.id << std::endl;
}
}
// get the 100 first responses from the queue
void consume()
{
int consumedCounter = 0;
for (;;)
{
if (!queue.empty())
{
std::cout << "consumed: " << queue.front().id << std::endl;
queue.pop();
consumedCounter++;
}
if (consumedCounter == 100)
break;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
std::thread t1(produce);
std::thread t2(consume);
t1.join();
t2.join();
return 0;
}
Everything seems to be working fine:
- No integrity violated / data corrupted
- The order of the elements in which the consumer gets them are correct (0<1<2<3<4...), of course the order in which the prod. and cons. are printing is random as there is no signaling involved.
Imagine you check for !queue.empty(), enter the next block and before getting to access queue.first(), another thread would remove (pop) the one and only element, so you query an empty queue.
Using a synchronized queue like the following
#pragma once
#include <queue>
#include <mutex>
#include <condition_variable>
template <typename T>
class SharedQueue
{
public:
SharedQueue();
~SharedQueue();
T& front();
void pop_front();
void push_back(const T& item);
void push_back(T&& item);
int size();
bool empty();
private:
std::deque<T> queue_;
std::mutex mutex_;
std::condition_variable cond_;
};
template <typename T>
SharedQueue<T>::SharedQueue(){}
template <typename T>
SharedQueue<T>::~SharedQueue(){}
template <typename T>
T& SharedQueue<T>::front()
{
std::unique_lock<std::mutex> mlock(mutex_);
while (queue_.empty())
{
cond_.wait(mlock);
}
return queue_.front();
}
template <typename T>
void SharedQueue<T>::pop_front()
{
std::unique_lock<std::mutex> mlock(mutex_);
while (queue_.empty())
{
cond_.wait(mlock);
}
queue_.pop_front();
}
template <typename T>
void SharedQueue<T>::push_back(const T& item)
{
std::unique_lock<std::mutex> mlock(mutex_);
queue_.push_back(item);
mlock.unlock(); // unlock before notificiation to minimize mutex con
cond_.notify_one(); // notify one waiting thread
}
template <typename T>
void SharedQueue<T>::push_back(T&& item)
{
std::unique_lock<std::mutex> mlock(mutex_);
queue_.push_back(std::move(item));
mlock.unlock(); // unlock before notificiation to minimize mutex con
cond_.notify_one(); // notify one waiting thread
}
template <typename T>
int SharedQueue<T>::size()
{
std::unique_lock<std::mutex> mlock(mutex_);
int size = queue_.size();
mlock.unlock();
return size;
}
The call to front() waits until it has an element and locks the underlying queue so only one thread may access it at a time.
My goal is to create a generic Event type that can be used in subscribe/notify architecture, but I am having trouble getting class member functions to work.
Event.hpp:
#ifndef EVENT_HPP
#define EVENT_HPP
#include <functional>
#include <unordered_set>
template <typename ... EventParameterTypes>
struct Event {
typedef void(*EventCallback)(EventParameterTypes ...);
template <typename ClassType>
Event &subscribe(ClassType *instance, void(ClassType::*eventCallback)(EventParameterTypes...)) {
auto bound = [=](EventParameterTypes&& ... params) { return ((instance)->*(eventCallback))(std::forward<EventParameterTypes>(params)...); };
return this->subscribe(bound);
}
Event &subscribe(EventCallback eventCallback) {
return this->addEventCallback(eventCallback);
}
void notify(EventParameterTypes ... types) {
for (const auto &it : this->m_eventCallbacks) {
if (it) (*it)(types...);
}
}
private:
std::unordered_set<EventCallback> m_eventCallbacks;
Event &addEventCallback(EventCallback eventCallback) {
auto foundIterator = std::find(this->m_eventCallbacks.begin(), this->m_eventCallbacks.end(), eventCallback);
if (foundIterator != this->m_eventCallbacks.end()) {
return *this;
}
this->m_eventCallbacks.insert(eventCallback);
return *this;
}
};
#endif //EVENT_HPP
Main.cpp:
#include "Event.hpp"
struct EventTest {
using MyEvent = Event<int>;
MyEvent myEvent;
};
void myEventCallback(int) {
//Stuff
}
struct EventListener {
void eventListenerCallback(int) {
//Stuff
}
};
int main() {
EventListener eventListener{};
EventTest eventTest{};
eventTest.myEvent.subscribe(&myEventCallback); //OK
eventTest.myEvent.subscribe(&eventListener, &EventListener::eventListenerCallback); //Compile error
}
Is there any way to resolve this? I have looked into std::bind but it only works with a certain amount of placeholders, and the lambda causes the function to be of a local lambda type.
You are hoping to somehow pack instance and eventCallback into a single function pointer. You can't do that, no more than you can pour an ocean into a thimble. One way out is to make EventCallback a typedef for a suitable specialication of std::function, as in
typedef std::function<void(EventParameterTypes ...)> EventCallback;
I won't be surprised if the rest of the code would just work after this, with no modifications.
Using #Igor Tandetnik s advice, I came up with the following (working) implementation:
Event.hpp
#ifndef EVENT_HPP
#define EVENT_HPP
#include <functional>
#include <functional>
#include <type_traits>
#include <vector>
template<typename T, typename... U>
size_t getFunctionAddress(std::function<T(U...)> f) {
typedef T(fnType)(U...);
fnType **fnPointer = f.template target<fnType *>();
if (fnPointer == nullptr) {
return 0;
}
return (size_t) *fnPointer;
}
template<typename ... EventParameterTypes>
struct Event {
template<class ClassType> using MemberPtr = void (ClassType::*)(EventParameterTypes...);
using EventCallback = std::function<void(EventParameterTypes ...)>;
template<class ClassType>
Event &subscribe(ClassType *instance, MemberPtr<ClassType> eventCallback) {
const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
return this->operator+=(bound);
}
Event &operator+=(EventCallback eventCallback) {
return this->addEventCallback(eventCallback);
}
template<class ClassType>
Event &unsubscribe(ClassType *instance, MemberPtr<ClassType> eventCallback) {
const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
return this->operator-=(bound);
}
Event &operator-=(EventCallback eventCallback) {
return this->removeEventCallback(eventCallback);
}
template<class ClassType>
bool isListenerRegistered(ClassType *instance, MemberPtr<ClassType> eventCallback) {
const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
return this->isListenerRegistered(bound);
}
bool isListenerRegistered(EventCallback eventCallback) {
return findListener(eventCallback) != this->m_eventCallbacks.cend();
}
void operator()(EventParameterTypes ... types) {
this->notify(std::forward<EventParameterTypes>(types)...);
}
void notify(EventParameterTypes ... types) {
for (const auto &it : this->m_eventCallbacks) {
if (it) (it)(std::forward<EventParameterTypes>(types)...);
}
}
private:
std::vector<EventCallback> m_eventCallbacks;
std::mutex m_eventListenerMutex;
typename std::vector<EventCallback>::const_iterator findListener(EventCallback eventCallback) {
for (auto iter = this->m_eventCallbacks.cbegin(); iter != this->m_eventCallbacks.cend(); iter++) {
if (getFunctionAddress(*iter) == getFunctionAddress(eventCallback)) {
return iter;
}
}
return this->m_eventCallbacks.cend();
}
Event &addEventCallback(EventCallback eventCallback) {
auto foundPosition = this->findListener(eventCallback);
if (foundPosition != this->m_eventCallbacks.cend()) {
return *this;
}
this->m_eventCallbacks.emplace_back(eventCallback);
return *this;
}
Event &removeEventCallback(EventCallback eventCallback) {
auto foundPosition = this->findListener(eventCallback);
if (foundPosition == this->m_eventCallbacks.cend()) {
return *this;
}
this->m_eventCallbacks.erase(foundPosition);
return *this;
}
};
#endif //EVENT_HPP
I am working on a messaging system on C++. I have;
class MessageData
{
public:
typedef std::vector<std::shared_ptr<MessageData>> MessageList;
virtual int getValue(std::shared_ptr<int>) { throw "Not implemented!"; };
virtual float getValue(std::shared_ptr<float>) { throw "Not implemented!"; };
virtual std::string getValue(std::shared_ptr<std::string>) { throw "Not implemented!"; };
...
...
virtual ~MessageData() {};
};
template <typename T>
class Message : public MessageData
{
T val;
public:
static std::shared_ptr<Message<T>> Make(T val) { return std::make_shared<Message<T>>(val); };
static T Get(std::shared_ptr<MessageData> in) { return in->getValue(std::make_shared<T>()); };
Message(T i) { val = i; };
T getValue(std::shared_ptr<T> out) override { return *out = val; }
~Message() {};
};
Using these, I can send/receive generic messages of different length conveniently using e.g;
sendMessage(MessageData::MessageList{
Message<std::string>::Make("paint"),
Message<int>::Make(14),
Message<float>::Make(129.3f),
...
});
Then I get the values;
sendMessage(MessageData::MessageList data) {
auto a = Message<std::string>::Get(data[0]);
auto b = Message<int>::Get(data[1]);
auto c = Message<float>::Get(data[2]);
...
}
The downside is that I have to list all the types I need to use in MessageData class. This isn't a big deal as I can limit the types I want to support but I'm really curious about how to templatize the type list without using a 3rd party library. Or is there a completely different and better method that I can use with similar clean syntax and type safety to pass messages around?
One way to make your code more generic is:
template <typename ... Ts>
class MessageDataImp;
template <typename T>
class MessageDataImp<T>
{
public:
virtual ~MessageDataImp() = default;
virtual T getValue(std::shared_ptr<T>) { throw "Not implemented!"; };
};
template <typename T, typename ... Ts>
class MessageDataImp<T, Ts...> : public MessageDataImp<T>, public MessageDataImp<Ts...>
{
public:
using MessageDataImp<T>::getValue;
using MessageDataImp<Ts...>::getValue;
};
template <typename ... Ts>
class MessageDataTs : public MessageDataImp<Ts...>
{
public:
typedef std::vector<std::shared_ptr<MessageDataTs<Ts...>>> MessageList;
};
using MessageData = MessageDataTs<int, float, std::string>;
I think I've developed a decent solution to my problem.
class MessageData {
public:
typedef std::vector<std::shared_ptr<MessageData>> MessageList;
virtual ~MessageData() {};
};
template<typename T>
class Message : public MessageData {
T val;
public:
template<typename U>
friend U GetMessage(std::shared_ptr<MessageData> in);
Message(T i) { val = i; };
};
template<typename T>
T GetMessage(std::shared_ptr<MessageData> in) {
std::shared_ptr<Message<T>> tmp = std::dynamic_pointer_cast<Message<T>>(in);
if (tmp) {
return tmp->val;
}
throw "Incorrect type!";
};
template<typename T>
std::shared_ptr<Message<T>> MakeMessage(T val)
{
return std::make_shared<Message<T>>(val);
};
Then send & receive values using;
sendMessage(MessageData::MessageList{
MakeMessage(std::string("paint")),
MakeMessage(14),
MakeMessage(129.3f),
...
});
sendMessage(MessageData::MessageList data) {
auto a = GetMessage<std::string>(data[0]);
auto b = GetMessage<int>(data[1]);
auto c = GetMessage<float>(data[2]);
...
}
Assuming that it's a simple multiple-reader, multiple-writer message bus based on a non-prioritised queue, I think I'd start with something like this:-
Note that I have used boost::variant/optional. These could easily be replaced with std:: versions if you have those available.
I have used variant because it efficiently caters for most use cases with compile-time safety.
The std/boost::any version would require significant (and possibly unwelcome) care for users of your bus.
#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <condition_variable>
#include <boost/variant.hpp>
#include <boost/optional.hpp>
template<class Mutex> auto get_lock(Mutex& m) { return std::unique_lock<Mutex>(m); }
template<class...Types>
struct message_bus
{
using message_type = boost::variant<Types...>;
void push(message_type msg) {
auto lock = get_lock(mutex_);
messages_.push(std::move(msg));
lock.unlock();
activity_.notify_one();
}
boost::optional<message_type> wait_pop()
{
boost::optional<message_type> result;
auto lock = get_lock(mutex_);
activity_.wait(lock, [this] { return this->stopped_ or not this->messages_.empty(); });
if (not messages_.empty())
{
result = std::move(messages_.front());
messages_.pop();
}
return result;
}
void signal_stop()
{
auto lock = get_lock(mutex_);
stopped_ = true;
lock.unlock();
activity_.notify_all();
}
std::queue<message_type> messages_;
std::mutex mutex_;
std::condition_variable activity_;
bool stopped_ = false;
};
static std::mutex emit_mutex;
template<class T>
void emit(const T& t)
{
auto lock = get_lock(emit_mutex);
std::cout << std::this_thread::get_id() << ": " << t << std::endl;;
}
int main()
{
using bus_type = message_bus<std::string, int>;
bus_type mb;
std::vector<std::thread> threads;
for (int i = 0 ; i < 10 ; ++i)
{
threads.emplace_back([&]
{
for(;;)
{
auto message = mb.wait_pop();
if (not message)
break;
boost::apply_visitor([](auto&& data) { emit(data); }, message.value());
}
});
}
for (int i = 0 ; i < 1000 ; ++i)
{
mb.push("string: " + std::to_string(i));
mb.push(i);
}
mb.signal_stop();
for (auto& t : threads) if (t.joinable()) t.join();
}
The topic says it. I don't understand why the std::queue (or in general: any queue) is not thread-safe by its nature, when there is no iterator involved as with other datastructures.
According to the common rule that
at least one thread is writing to ...
and another thread is reading from a shared resource
I should have gotten a conflict in the following example code:
#include "stdafx.h"
#include <queue>
#include <thread>
#include <iostream>
struct response
{
static int & getCount()
{
static int theCount = 0;
return theCount;
}
int id;
};
std::queue<response> queue;
// generate 100 response objects and push them into the queue
void produce()
{
for (int i = 0; i < 100; i++)
{
response r;
r.id = response::getCount()++;
queue.push(r);
std::cout << "produced: " << r.id << std::endl;
}
}
// get the 100 first responses from the queue
void consume()
{
int consumedCounter = 0;
for (;;)
{
if (!queue.empty())
{
std::cout << "consumed: " << queue.front().id << std::endl;
queue.pop();
consumedCounter++;
}
if (consumedCounter == 100)
break;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
std::thread t1(produce);
std::thread t2(consume);
t1.join();
t2.join();
return 0;
}
Everything seems to be working fine:
- No integrity violated / data corrupted
- The order of the elements in which the consumer gets them are correct (0<1<2<3<4...), of course the order in which the prod. and cons. are printing is random as there is no signaling involved.
Imagine you check for !queue.empty(), enter the next block and before getting to access queue.first(), another thread would remove (pop) the one and only element, so you query an empty queue.
Using a synchronized queue like the following
#pragma once
#include <queue>
#include <mutex>
#include <condition_variable>
template <typename T>
class SharedQueue
{
public:
SharedQueue();
~SharedQueue();
T& front();
void pop_front();
void push_back(const T& item);
void push_back(T&& item);
int size();
bool empty();
private:
std::deque<T> queue_;
std::mutex mutex_;
std::condition_variable cond_;
};
template <typename T>
SharedQueue<T>::SharedQueue(){}
template <typename T>
SharedQueue<T>::~SharedQueue(){}
template <typename T>
T& SharedQueue<T>::front()
{
std::unique_lock<std::mutex> mlock(mutex_);
while (queue_.empty())
{
cond_.wait(mlock);
}
return queue_.front();
}
template <typename T>
void SharedQueue<T>::pop_front()
{
std::unique_lock<std::mutex> mlock(mutex_);
while (queue_.empty())
{
cond_.wait(mlock);
}
queue_.pop_front();
}
template <typename T>
void SharedQueue<T>::push_back(const T& item)
{
std::unique_lock<std::mutex> mlock(mutex_);
queue_.push_back(item);
mlock.unlock(); // unlock before notificiation to minimize mutex con
cond_.notify_one(); // notify one waiting thread
}
template <typename T>
void SharedQueue<T>::push_back(T&& item)
{
std::unique_lock<std::mutex> mlock(mutex_);
queue_.push_back(std::move(item));
mlock.unlock(); // unlock before notificiation to minimize mutex con
cond_.notify_one(); // notify one waiting thread
}
template <typename T>
int SharedQueue<T>::size()
{
std::unique_lock<std::mutex> mlock(mutex_);
int size = queue_.size();
mlock.unlock();
return size;
}
The call to front() waits until it has an element and locks the underlying queue so only one thread may access it at a time.