这是我在C++20中实现的线程队列:
#pragma once
#include <deque>
#include <mutex>
#include <condition_variable>
#include <utility>
#include <concepts>
#include <list>
template<typename QueueType>
concept thread_queue_concept =
std::same_as<QueueType, std::deque<typename QueueType::value_type, typename QueueType::allocator_type>>
|| std::same_as<QueueType, std::list<typename QueueType::value_type, typename QueueType::allocator_type>>;
template<typename QueueType>
requires thread_queue_concept<QueueType>
struct thread_queue
{
using value_type = typename QueueType::value_type;
thread_queue();
explicit thread_queue( typename QueueType::allocator_type const &alloc );
thread_queue( thread_queue &&other );
thread_queue &operator =( thread_queue const &other );
thread_queue &operator =( thread_queue &&other );
bool empty() const;
std::size_t size() const;
void shrink_to_fit();
void clear();
template<typename ... Args>
requires std::is_constructible_v<typename QueueType::value_type, Args ...>
void enque( Args &&... args );
template<typename Producer>
requires requires( Producer producer ) { { producer() } -> std::same_as<std::pair<bool, typename QueueType::value_type>>; }
void enqueue_multiple( Producer producer );
template<typename Consumer>
requires requires( Consumer consumer, typename QueueType::value_type value ) { { consumer( std::move( value ) ) } -> std::same_as<bool>; }
void dequeue_multiple( Consumer consumer );
typename QueueType::value_type dequeue();
void swap( thread_queue &other );
private:
mutable std::mutex m_mtx;
mutable std::condition_variable m_cv;
QueueType m_queue;
};
template<typename QueueType>
requires thread_queue_concept<QueueType>
thread_queue<QueueType>::thread_queue()
{
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
thread_queue<QueueType>::thread_queue( typename QueueType::allocator_type const &alloc ) :
m_queue( alloc )
{
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
thread_queue<QueueType>::thread_queue( thread_queue &&other )
{
using namespace std;
lock_guard lock( other.m_mtx );
m_queue = move( other.m_queue );
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
thread_queue<QueueType> &thread_queue<QueueType>::thread_queue::operator =( thread_queue const &other )
{
std::lock_guard
ourLock( m_mtx ),
otherLock( other.m_mtx );
m_queue = other.m_queue;
return *this;
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
thread_queue<QueueType> &thread_queue<QueueType>::thread_queue::operator =( thread_queue &&other )
{
using namespace std;
lock_guard
ourLock( m_mtx ),
otherLock( other.m_mtx );
m_queue = move( other.m_queue );
return *this;
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
bool thread_queue<QueueType>::thread_queue::empty() const
{
std::lock_guard lock( m_mtx );
return m_queue.empty();
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
std::size_t thread_queue<QueueType>::thread_queue::size() const
{
std::lock_guard lock( m_mtx );
return m_queue.size();
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
void thread_queue<QueueType>::thread_queue::shrink_to_fit()
{
std::lock_guard lock( m_mtx );
return m_queue.shrink_to_fit();
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
void thread_queue<QueueType>::thread_queue::clear()
{
std::lock_guard lock( m_mtx );
m_queue.clear();
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
template<typename ... Args>
requires std::is_constructible_v<typename QueueType::value_type, Args ...>
void thread_queue<QueueType>::thread_queue::enque( Args &&... args )
{
using namespace std;
unique_lock lock( m_mtx );
m_queue.emplace_front( forward<Args>( args ) ... );
m_cv.notify_one();
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
typename QueueType::value_type thread_queue<QueueType>::thread_queue::dequeue()
{
using namespace std;
unique_lock lock( m_mtx );
while( m_queue.empty() )
m_cv.wait( lock );
value_type value = move( m_queue.back() );
m_queue.pop_back();
return value;
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
template<typename Producer>
requires requires( Producer producer ) { { producer() } -> std::same_as<std::pair<bool, typename QueueType::value_type>>; }
void thread_queue<QueueType>::enqueue_multiple( Producer producer )
{
using namespace std;
lock_guard lock( m_mtx );
for( std::pair<bool, value_type> ret; (ret = move( producer() )).first; )
m_queue.emplace_front( move( ret.second ) ),
m_cv.notify_one();
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
template<typename Consumer>
requires requires( Consumer consumer, typename QueueType::value_type value ) { { consumer( std::move( value ) ) } -> std::same_as<bool>; }
void thread_queue<QueueType>::dequeue_multiple( Consumer consumer )
{
using namespace std;
unique_lock lock( m_mtx );
for( ; ; )
{
while( m_queue.empty() )
m_cv.wait( lock );
try
{
bool cont = consumer( move( m_queue.back() ) );
m_queue.pop_back();
if( !cont )
return;
}
catch( ... )
{
m_queue.pop_back();
throw;
}
}
}
template<typename QueueType>
requires thread_queue_concept<QueueType>
void thread_queue<QueueType>::thread_queue::swap( thread_queue &other )
{
std::lock_guard
ourLock( m_mtx ),
otherLock( other.m_mtx );
m_queue.swap( other.m_queue );
}
唯一的模板参数是BaseType,它可以是std::deque类型或std::list类型,并受thread_queue_concept限制。这个类将此类型用作内部队列类型。选择一个最适合你的应用程序的BaseType。我可能已经在更具区分性的thread_queue_concepts上限制了该类,以检查BaseType的所有使用部分,以便该类可能适用于与std::list<>或std::deque<>兼容的其他类型,但我懒得为这种不太可能出现的情况实现它。
这段代码的优点之一是enqueue_multiple和dequeue_multiple。这些函数被赋予了一个函数对象,通常是一个lambda,它可以在只有一个锁定步骤的情况下排队或出队多个项目。对于enqueue,这总是成立的,对于dequeue,这取决于队列是否有要提取的元素。
如果你有一个生产者和多个消费者,那么enqueue_multiple通常是有意义的。它会导致较长时间的锁定期,因此仅在项能够快速生产或移动时才有意义。
如果你有多个生产者和一个消费者,那么dequeue_multiple通常是有意义的。在这里,我们也有较长的锁定期,但由于对象通常只是快速移动,所以通常不会有影响。
如果dequeue_multiple的消费者函数对象在消费时抛出异常,那么异常会被捕获,并且提供给消费者的元素(作为底层队列类型对象内的右值引用)将被删除。
如果你想使用C++11和这个类,你必须删除概念或使用#if defined(__cpp_concepts)禁用它们。
filename
?我在这里看不出任何理由。 - Tony The Lionstd::move
,并通过值而不是非const右值引用获取enqueue
的filename
参数。目前,它只能被调用为右值,这可能不是您想要的。 - Ben Hymersstd::move
;在这里使用它是没有意义的,因为您已经有了一个右值引用,而所有std::move
所做的就是将其转换为一个。在这里声明一个按值传递的参数,然后使用std::move
将其推入队列是理想的,因为调用者可以通过值传递(在调用之前进行复制,然后通过std::move
将该副本移动到队列中)或通过右值引用(调用者使用std::move
,在这种情况下,移动构造函数被调用来构造参数,然后再次std::move
推送到队列中。 - WhozCraig