我正在研究几个C ++ 11工作队列类。第一个类command_queue是一个多生产者单个使用者工作队列。多个线程可以发布命令,并且单个线程在循环中调用“wait()”和“pop_back()”来处理这些命令。
第二个类,Actor使用command_queue并且实际上提供了一个消费者线程...除此之外,想法是post()将返回一个未来,以便客户端可以阻塞直到命令被处理,或者继续运行(actor)还添加了结果类型的想法)。为了实现这一点,我试图将std :: promise存储在工作队列中的std :: pair中。我相信我非常接近,但我在下面的_entry_point函数中遇到了问题...具体来说,当我试图从命令队列中获取std ::对时,我得到了“使用删除”函数“编译错误...我将把我从代码下面的编译器得到的实际错误(你应该能够将它保存到文本文件并自己编译,它是独立的c ++ 11代码)
#include <mutex>
#include <condition_variable>
#include <future>
#include <list>
#include <stdio.h>
template<class T>
class command_queue
{
public:
command_queue() = default;
command_queue( const command_queue& ) = delete;
virtual ~command_queue() noexcept = default;
command_queue& operator = ( const command_queue& ) = delete;
void start()
{
std::unique_lock<std::recursive_mutex> g( _queueLock );
_started = true;
}
bool started()
{
return _started;
}
void stop()
{
std::unique_lock<std::recursive_mutex> g( _queueLock );
_started = false;
_queueCond.notify_one();
}
void post_front( const T& cmd )
{
std::unique_lock<std::recursive_mutex> g( _queueLock );
_queue.push_front( cmd );
_queueCond.notify_one();
}
void post_front( T&& cmd )
{
std::unique_lock<std::recursive_mutex> g( _queueLock );
_queue.push_front( cmd );
_queueCond.notify_one();
}
void wait()
{
std::unique_lock<std::recursive_mutex> g( _queueLock );
_queueCond.wait( g, [this](){return !this->_queue.empty() ? true : !this->_started;});
}
T pop_back()
{
std::unique_lock<std::recursive_mutex> g( _queueLock );
auto val = _queue.back();
_queue.pop_back();
return val;
}
private:
std::recursive_mutex _queueLock;
std::condition_variable_any _queueCond;
std::list<T> _queue;
bool _started = false;
};
template<class T, class U>
class actor
{
public:
actor() :
_started( false ),
_thread(),
_queue()
{
}
actor( const actor& ) = delete;
virtual ~actor() noexcept
{
if( _started )
stop();
}
actor& operator = ( const actor& ) = delete;
void start()
{
_started = true;
_queue.start();
_thread = std::thread( &actor<T,U>::_entry_point, this );
}
void stop()
{
_started = false;
_queue.stop();
_thread.join();
}
std::future<U> post( const T& cmd )
{
std::promise<U> p;
std::future<U> waiter = p.get_future();
_queue.post_front( std::pair<T,std::promise<U>>(cmd, std::move(p)) );
return waiter;
}
virtual U process( const T& cmd ) = 0;
protected:
void _entry_point()
{
while( _started )
{
_queue.wait();
if( !_started )
continue;
std::pair<T,std::promise<U>> item = _queue.pop_back();
item.second.set_value( process( item.first ) );
}
}
bool _started;
std::thread _thread;
command_queue<std::pair<T,std::promise<U>>> _queue;
};
class int_printer : public actor<int,bool>
{
public:
virtual bool process( const int& cmd ) override
{
printf("%d",cmd);
return true;
}
};
using namespace std;
int main( int argc, char* argv[] )
{
// std::promise<bool> p;
// list<std::pair<int,std::promise<bool>>> promises;
// promises.push_back( make_pair<int,std::promise<bool>>(10,std::move(p)) );
int_printer a;
a.start();
future<bool> result = a.post( 10 );
a.stop();
}
[developer@0800275b874e projects]$ g++ -std=c++11 pf.cpp -opf -lpthread
pf.cpp: In instantiation of ‘T command_queue<T>::pop_back() [with T = std::pair<int, std::promise<bool> >]’:
pf.cpp:133:65: required from ‘void actor<T, U>::_entry_point() [with T = int; U = bool]’
pf.cpp:99:9: required from ‘void actor<T, U>::start() [with T = int; U = bool]’
pf.cpp:163:13: required from here
pf.cpp:60:32: error: use of deleted function ‘constexpr std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2>&) [with _T1 = int; _T2 = std::promise<bool>]’
In file included from /usr/lib/gcc/x86_64-redhat-linux/4.7.0/../../../../include/c++/4.7.0/utility:72:0,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.0/../../../../include/c++/4.7.0/tuple:38,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.0/../../../../include/c++/4.7.0/mutex:39,
from pf.cpp:2:
/usr/lib/gcc/x86_64-redhat-linux/4.7.0/../../../../include/c++/4.7.0/bits/stl_pair.h:119:17: note: ‘constexpr std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2>&) [with _T1 = int; _T2 = std::promise<bool>]’ is implicitly deleted because the default definition would be ill-formed:
/usr/lib/gcc/x86_64-redhat-linux/4.7.0/../../../../include/c++/4.7.0/bits/stl_pair.h:119:17: error: use of deleted function ‘std::promise<_Res>::promise(const std::promise<_Res>&) [with _Res = bool]’
In file included from pf.cpp:4:0:
/usr/lib/gcc/x86_64-redhat-linux/4.7.0/../../../../include/c++/4.7.0/future:963:7: error: declared here
In file included from /usr/lib/gcc/x86_64-redhat-linux/4.7.0/../../../../include/c++/4.7.0/list:64:0,
from pf.cpp:5:
答案 0 :(得分:2)
Promise不可复制(这是有道理的 - 它们代表一种独特的状态)。您需要在多个地方使用std::move
来转移承诺的唯一所有权。
具体来说,您的家庭酿造队列类需要允许移动,例如
auto val = std::move(_queue.back());
_queue.pop_back();
return val;
答案 1 :(得分:1)
您使用command_queue::_started
保护对_queueLock
的写入,但不保留command_queue::started()
中的读取;如果某个线程可以在另一个线程正在执行修改(例如started
)时调用stop()
,则这是数据争用。
几个小小的观察结果:
它不会使您的程序不正确,但最好通知外部的互斥锁。如果你通过持有的互斥锁通知,另一个核心可能会浪费一两微秒或两个调度等待线程只运行立即阻塞互斥锁。
由于缺少post_front(T&&)
,您的std::move
将传递的项目复制:
_queue.push_front( cmd );
必须是
_queue.push_front( std::move( cmd ) );
如果您希望将其实际移入队列。
条件变量wait的谓词可以从
简化[this](){return !this->_queue.empty() ? true : !this->_started;}
到
[this]{return !_queue.empty() || !_started;}
command_queue
个成员函数都没有调用其他command_queue
函数,因此您可以使用普通std::mutex
代替std::recursive_mutex
和std::condition_variable
代替std::condition_variable_any
。
您可以使用std::lock_guard<std::mutex>
代替std::unique_lock<std::mutex>
来锁定除wait
之外的每个成员函数中的互斥锁。它的重量要轻得多。
您遇到传统的pop
异常安全问题:如果T
的所选移动/复制构造函数在修改队列后从pop_back
返回时失败并出现异常,那个元素丢了。你编写函数的方式使得极的可能性很小,因为
auto val = _queue.back();
_queue.pop_back();
return val;
(或Kerrek's fix之后)
auto val = std::move(_queue.back());
_queue.pop_back();
return val;
应该有资格使用合适的编译器进行复制省略,在pop_back
发生之前就地构造返回的对象。请注意,如果将来的更改阻碍了复制省略,您将引入异常安全问题。您可以通过传递T&
或optional<T>&
作为参数并将结果移动到该参数来完全避免此问题。
actor::_started
是不必要的,因为它实际上是actor::_queue::_started
的代理。