# librf 2.0 | |||||
# librf 2.4 | |||||
### librf - 协程库 | ### librf - 协程库 | ||||
2020-03-08 更新: | |||||
更新channel实现,效率提高了近三倍。 | |||||
channel的新的实现方法,为event/mutex指明了新的修改方向。 | |||||
2020-02-16 更新: | 2020-02-16 更新: | ||||
更新调度器算法,深入应用Coroutines的特性,以期获得更高调度性能。 | 更新调度器算法,深入应用Coroutines的特性,以期获得更高调度性能。 | ||||
不再支持C++14。 | 不再支持C++14。 | ||||
#pragma once | #pragma once | ||||
#include "intrusive_link_queue.h" | |||||
RESUMEF_NS | RESUMEF_NS | ||||
{ | { | ||||
struct channel_impl_v2 : public std::enable_shared_from_this<channel_impl_v2<_Ty>> | struct channel_impl_v2 : public std::enable_shared_from_this<channel_impl_v2<_Ty>> | ||||
{ | { | ||||
using value_type = _Ty; | using value_type = _Ty; | ||||
struct state_read_t; | |||||
struct state_write_t; | |||||
struct state_channel_t; | |||||
using state_read_t = state_channel_t; | |||||
using state_write_t = state_channel_t; | |||||
channel_impl_v2(size_t max_counter_); | channel_impl_v2(size_t max_counter_); | ||||
bool try_read(value_type& val); | bool try_read(value_type& val); | ||||
bool add_read_list(state_read_t* state); | |||||
bool try_read_nolock(value_type& val); | |||||
void add_read_list_nolock(state_read_t* state); | |||||
bool try_write(value_type& val); | bool try_write(value_type& val); | ||||
bool add_write_list(state_write_t* state); | |||||
bool try_write_nolock(value_type& val); | |||||
void add_write_list_nolock(state_write_t* state); | |||||
size_t capacity() const noexcept | size_t capacity() const noexcept | ||||
{ | { | ||||
struct state_channel_t : public state_base_t | struct state_channel_t : public state_base_t | ||||
{ | { | ||||
state_channel_t(channel_impl_v2* ch, value_type& val) noexcept | state_channel_t(channel_impl_v2* ch, value_type& val) noexcept | ||||
: m_channel(ch->shared_from_this()) | |||||
: _channel(ch->shared_from_this()) | |||||
, _value(&val) | , _value(&val) | ||||
{ | { | ||||
} | } | ||||
std::rethrow_exception(std::make_exception_ptr(channel_exception{ _error })); | std::rethrow_exception(std::make_exception_ptr(channel_exception{ _error })); | ||||
} | } | ||||
} | } | ||||
public: | |||||
state_channel_t* _next = nullptr; | |||||
protected: | protected: | ||||
friend channel_impl_v2; | friend channel_impl_v2; | ||||
std::shared_ptr<channel_impl_v2> m_channel; | |||||
state_channel_t* m_next = nullptr; | |||||
std::shared_ptr<channel_impl_v2> _channel; | |||||
value_type* _value; | value_type* _value; | ||||
error_code _error = error_code::none; | error_code _error = error_code::none; | ||||
}; | }; | ||||
struct state_read_t : public state_channel_t | |||||
{ | |||||
using state_channel_t::state_channel_t; | |||||
//将自己加入到通知链表里 | |||||
template<class _PromiseT, typename = std::enable_if_t<is_promise_v<_PromiseT>>> | |||||
bool on_await_suspend(coroutine_handle<_PromiseT> handler) noexcept | |||||
{ | |||||
state_channel_t::on_await_suspend(handler); | |||||
if (!this->m_channel->add_read_list(this)) | |||||
{ | |||||
this->_coro = nullptr; | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
}; | |||||
struct state_write_t : public state_channel_t | |||||
{ | |||||
using state_channel_t::state_channel_t; | |||||
//将自己加入到通知链表里 | |||||
template<class _PromiseT, typename = std::enable_if_t<is_promise_v<_PromiseT>>> | |||||
bool on_await_suspend(coroutine_handle<_PromiseT> handler) noexcept | |||||
{ | |||||
state_channel_t::on_await_suspend(handler); | |||||
if (!this->m_channel->add_write_list(this)) | |||||
{ | |||||
this->_coro = nullptr; | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
}; | |||||
private: | private: | ||||
auto try_pop_reader_()->state_read_t*; | |||||
auto try_pop_writer_()->state_write_t*; | |||||
void awake_one_reader_(); | void awake_one_reader_(); | ||||
bool awake_one_reader_(value_type& val); | |||||
void awake_one_writer_(); | void awake_one_writer_(); | ||||
bool awake_one_writer_(value_type & val); | |||||
channel_impl_v2(const channel_impl_v2&) = delete; | channel_impl_v2(const channel_impl_v2&) = delete; | ||||
channel_impl_v2(channel_impl_v2&&) = delete; | channel_impl_v2(channel_impl_v2&&) = delete; | ||||
static constexpr bool USE_SPINLOCK = true; | static constexpr bool USE_SPINLOCK = true; | ||||
static constexpr bool USE_RING_QUEUE = true; | static constexpr bool USE_RING_QUEUE = true; | ||||
static constexpr bool USE_LINK_QUEUE = true; | |||||
using lock_type = std::conditional_t<USE_SPINLOCK, spinlock, std::deque<std::recursive_mutex>>; | |||||
//using queue_type = std::conditional_t<USE_RING_QUEUE, ring_queue_spinlock<value_type, false, uint32_t>, std::deque<value_type>>; | //using queue_type = std::conditional_t<USE_RING_QUEUE, ring_queue_spinlock<value_type, false, uint32_t>, std::deque<value_type>>; | ||||
using queue_type = std::conditional_t<USE_RING_QUEUE, ring_queue<value_type, false, uint32_t>, std::deque<value_type>>; | using queue_type = std::conditional_t<USE_RING_QUEUE, ring_queue<value_type, false, uint32_t>, std::deque<value_type>>; | ||||
using read_queue_type = std::conditional_t<USE_LINK_QUEUE, intrusive_link_queue<state_channel_t>, std::list<state_read_t*>>; | |||||
using write_queue_type = std::conditional_t<USE_LINK_QUEUE, intrusive_link_queue<state_channel_t>, std::list<state_write_t*>>; | |||||
const size_t _max_counter; //数据队列的容量上限 | const size_t _max_counter; //数据队列的容量上限 | ||||
public: | |||||
using lock_type = std::conditional_t<USE_SPINLOCK, spinlock, std::deque<std::recursive_mutex>>; | |||||
lock_type _lock; //保证访问本对象是线程安全的 | lock_type _lock; //保证访问本对象是线程安全的 | ||||
private: | |||||
queue_type _values; //数据队列 | queue_type _values; //数据队列 | ||||
std::list<state_read_t*> _read_awakes; //读队列 | |||||
std::list<state_write_t*> _write_awakes; //写队列 | |||||
read_queue_type _read_awakes; //读队列 | |||||
write_queue_type _write_awakes; //写队列 | |||||
}; | }; | ||||
template<class _Ty> | template<class _Ty> | ||||
channel_impl_v2<_Ty>::channel_impl_v2(size_t max_counter_) | channel_impl_v2<_Ty>::channel_impl_v2(size_t max_counter_) | ||||
: _max_counter(max_counter_) | : _max_counter(max_counter_) | ||||
, _values(USE_RING_QUEUE ? (std::max)((size_t)1, max_counter_) : 0) | |||||
, _values(USE_RING_QUEUE ? max_counter_ : 0) | |||||
{ | |||||
} | |||||
template<class _Ty> | |||||
inline bool channel_impl_v2<_Ty>::try_read(value_type& val) | |||||
{ | { | ||||
scoped_lock<lock_type> lock_(this->_lock); | |||||
return try_read_nolock(val); | |||||
} | } | ||||
template<class _Ty> | template<class _Ty> | ||||
bool channel_impl_v2<_Ty>::try_read(value_type& val) | |||||
bool channel_impl_v2<_Ty>::try_read_nolock(value_type& val) | |||||
{ | { | ||||
if constexpr (USE_RING_QUEUE) | if constexpr (USE_RING_QUEUE) | ||||
{ | { | ||||
scoped_lock<lock_type> lock_(this->_lock); | |||||
if (_values.try_pop(val)) | if (_values.try_pop(val)) | ||||
{ | { | ||||
awake_one_writer_(); | awake_one_writer_(); | ||||
return true; | return true; | ||||
} | } | ||||
return false; | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
scoped_lock<lock_type> lock_(this->_lock); | |||||
if (_values.size() > 0) | if (_values.size() > 0) | ||||
{ | { | ||||
val = std::move(_values.front()); | val = std::move(_values.front()); | ||||
return true; | return true; | ||||
} | } | ||||
return false; | |||||
} | } | ||||
return awake_one_writer_(val); | |||||
} | } | ||||
template<class _Ty> | template<class _Ty> | ||||
bool channel_impl_v2<_Ty>::add_read_list(state_read_t* state) | |||||
inline void channel_impl_v2<_Ty>::add_read_list_nolock(state_read_t* state) | |||||
{ | { | ||||
assert(state != nullptr); | assert(state != nullptr); | ||||
_read_awakes.push_back(state); | |||||
} | |||||
if constexpr (USE_RING_QUEUE) | |||||
{ | |||||
scoped_lock<lock_type> lock_(this->_lock); | |||||
if (_values.try_pop(*state->_value)) | |||||
{ | |||||
awake_one_writer_(); | |||||
return false; | |||||
} | |||||
_read_awakes.push_back(state); | |||||
awake_one_writer_(); | |||||
return true; | |||||
} | |||||
else | |||||
{ | |||||
scoped_lock<lock_type> lock_(this->_lock); | |||||
if (_values.size() > 0) | |||||
{ | |||||
*state->_value = std::move(_values.front()); | |||||
_values.pop_front(); | |||||
awake_one_writer_(); | |||||
return false; | |||||
} | |||||
_read_awakes.push_back(state); | |||||
awake_one_writer_(); | |||||
return true; | |||||
} | |||||
template<class _Ty> | |||||
inline bool channel_impl_v2<_Ty>::try_write(value_type& val) | |||||
{ | |||||
scoped_lock<lock_type> lock_(this->_lock); | |||||
return try_write_nolock(val); | |||||
} | } | ||||
template<class _Ty> | template<class _Ty> | ||||
bool channel_impl_v2<_Ty>::try_write(value_type& val) | |||||
bool channel_impl_v2<_Ty>::try_write_nolock(value_type& val) | |||||
{ | { | ||||
if constexpr (USE_RING_QUEUE) | if constexpr (USE_RING_QUEUE) | ||||
{ | { | ||||
scoped_lock<lock_type> lock_(this->_lock); | |||||
if (_values.try_push(std::move(val))) | if (_values.try_push(std::move(val))) | ||||
{ | { | ||||
awake_one_reader_(); | awake_one_reader_(); | ||||
return true; | return true; | ||||
} | } | ||||
return false; | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
scoped_lock<lock_type> lock_(this->_lock); | |||||
if (_values.size() < _max_counter) | if (_values.size() < _max_counter) | ||||
{ | { | ||||
_values.push_back(std::move(val)); | _values.push_back(std::move(val)); | ||||
return true; | return true; | ||||
} | } | ||||
return false; | |||||
} | } | ||||
return awake_one_reader_(val); | |||||
} | } | ||||
template<class _Ty> | template<class _Ty> | ||||
bool channel_impl_v2<_Ty>::add_write_list(state_write_t* state) | |||||
inline void channel_impl_v2<_Ty>::add_write_list_nolock(state_write_t* state) | |||||
{ | { | ||||
assert(state != nullptr); | assert(state != nullptr); | ||||
_write_awakes.push_back(state); | |||||
} | |||||
if constexpr (USE_RING_QUEUE) | |||||
template<class _Ty> | |||||
auto channel_impl_v2<_Ty>::try_pop_reader_()->state_read_t* | |||||
{ | |||||
if constexpr (USE_LINK_QUEUE) | |||||
{ | { | ||||
scoped_lock<lock_type> lock_(this->_lock); | |||||
if (_values.try_push(std::move(*state->_value))) | |||||
return reinterpret_cast<state_read_t*>(_read_awakes.try_pop()); | |||||
} | |||||
else | |||||
{ | |||||
if (!_read_awakes.empty()) | |||||
{ | { | ||||
awake_one_reader_(); | |||||
return false; | |||||
state_write_t* state = _read_awakes.front(); | |||||
_read_awakes.pop_front(); | |||||
return state; | |||||
} | } | ||||
return nullptr; | |||||
} | |||||
} | |||||
_write_awakes.push_back(state); | |||||
awake_one_reader_(); | |||||
return true; | |||||
template<class _Ty> | |||||
auto channel_impl_v2<_Ty>::try_pop_writer_()->state_write_t* | |||||
{ | |||||
if constexpr (USE_LINK_QUEUE) | |||||
{ | |||||
return reinterpret_cast<state_write_t*>(_write_awakes.try_pop()); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
scoped_lock<lock_type> lock_(this->_lock); | |||||
if (_values.size() < _max_counter) | |||||
if (!_write_awakes.empty()) | |||||
{ | { | ||||
_values.push_back(std::move(*state->_value)); | |||||
awake_one_reader_(); | |||||
return false; | |||||
state_write_t* state = _write_awakes.front(); | |||||
_write_awakes.pop_front(); | |||||
return state; | |||||
} | } | ||||
_write_awakes.push_back(state); | |||||
awake_one_reader_(); | |||||
return true; | |||||
return nullptr; | |||||
} | } | ||||
} | } | ||||
template<class _Ty> | template<class _Ty> | ||||
void channel_impl_v2<_Ty>::awake_one_reader_() | void channel_impl_v2<_Ty>::awake_one_reader_() | ||||
{ | { | ||||
for (auto iter = _read_awakes.begin(); iter != _read_awakes.end(); ) | |||||
state_read_t* state = try_pop_reader_(); | |||||
if (state != nullptr) | |||||
{ | { | ||||
state_read_t* state = *iter; | |||||
iter = _read_awakes.erase(iter); | |||||
if constexpr (USE_RING_QUEUE) | if constexpr (USE_RING_QUEUE) | ||||
{ | { | ||||
if (!_values.try_pop(*state->_value)) | if (!_values.try_pop(*state->_value)) | ||||
state->_error = error_code::read_before_write; | state->_error = error_code::read_before_write; | ||||
} | } | ||||
} | } | ||||
state->on_notify(); | state->on_notify(); | ||||
} | |||||
} | |||||
template<class _Ty> | |||||
bool channel_impl_v2<_Ty>::awake_one_reader_(value_type& val) | |||||
{ | |||||
state_read_t* state = try_pop_reader_(); | |||||
if (state != nullptr) | |||||
{ | |||||
*state->_value = std::move(val); | |||||
break; | |||||
state->on_notify(); | |||||
return true; | |||||
} | } | ||||
return false; | |||||
} | } | ||||
template<class _Ty> | template<class _Ty> | ||||
void channel_impl_v2<_Ty>::awake_one_writer_() | void channel_impl_v2<_Ty>::awake_one_writer_() | ||||
{ | { | ||||
for (auto iter = _write_awakes.begin(); iter != _write_awakes.end(); ) | |||||
state_write_t* state = try_pop_writer_(); | |||||
if (state != nullptr) | |||||
{ | { | ||||
state_write_t* state = std::move(*iter); | |||||
iter = _write_awakes.erase(iter); | |||||
if constexpr (USE_RING_QUEUE) | if constexpr (USE_RING_QUEUE) | ||||
{ | { | ||||
bool ret = _values.try_push(std::move(*state->_value)); | bool ret = _values.try_push(std::move(*state->_value)); | ||||
assert(_values.size() < _max_counter); | assert(_values.size() < _max_counter); | ||||
_values.push_back(*state->_value); | _values.push_back(*state->_value); | ||||
} | } | ||||
state->on_notify(); | state->on_notify(); | ||||
} | |||||
} | |||||
break; | |||||
template<class _Ty> | |||||
bool channel_impl_v2<_Ty>::awake_one_writer_(value_type& val) | |||||
{ | |||||
state_write_t* writer = try_pop_writer_(); | |||||
if (writer != nullptr) | |||||
{ | |||||
val = std::move(*writer->_value); | |||||
writer->on_notify(); | |||||
return true; | |||||
} | } | ||||
return false; | |||||
} | } | ||||
} //namespace detail | } //namespace detail | ||||
inline namespace channel_v2 | inline namespace channel_v2 | ||||
{ | { | ||||
template<class _Ty> | |||||
template<class _Ty = bool> | |||||
struct channel_t | struct channel_t | ||||
{ | { | ||||
using value_type = _Ty; | using value_type = _Ty; | ||||
using channel_type = detail::channel_impl_v2<value_type>; | using channel_type = detail::channel_impl_v2<value_type>; | ||||
using lock_type = typename channel_type::lock_type; | |||||
channel_t(size_t max_counter = 0) | channel_t(size_t max_counter = 0) | ||||
:_chan(std::make_shared<channel_type>(max_counter)) | :_chan(std::make_shared<channel_type>(max_counter)) | ||||
return _chan->capacity(); | return _chan->capacity(); | ||||
} | } | ||||
struct read_awaiter | |||||
struct [[nodiscard]] read_awaiter | |||||
{ | { | ||||
using state_type = typename channel_type::state_read_t; | using state_type = typename channel_type::state_read_t; | ||||
_channel->try_read(_value); | _channel->try_read(_value); | ||||
} | } | ||||
bool await_ready() | |||||
bool await_ready() const noexcept | |||||
{ | { | ||||
if (_channel->try_read(static_cast<value_type&>(_value))) | |||||
{ | |||||
_channel = nullptr; | |||||
return true; | |||||
} | |||||
return false; | return false; | ||||
} | } | ||||
template<class _PromiseT, typename = std::enable_if_t<is_promise_v<_PromiseT>>> | template<class _PromiseT, typename = std::enable_if_t<is_promise_v<_PromiseT>>> | ||||
bool await_suspend(coroutine_handle<_PromiseT> handler) | bool await_suspend(coroutine_handle<_PromiseT> handler) | ||||
{ | { | ||||
scoped_lock<lock_type> lock_(_channel->_lock); | |||||
if (_channel->try_read_nolock(_value)) | |||||
{ | |||||
_channel = nullptr; | |||||
return false; | |||||
} | |||||
_state = new state_type(_channel, static_cast<value_type&>(_value)); | _state = new state_type(_channel, static_cast<value_type&>(_value)); | ||||
_state->on_await_suspend(handler); | |||||
_channel->add_read_list_nolock(_state.get()); | |||||
_channel = nullptr; | _channel = nullptr; | ||||
return _state->on_await_suspend(handler); | |||||
return true; | |||||
} | } | ||||
value_type await_resume() | value_type await_resume() | ||||
{ | { | ||||
return { _chan.get() }; | return { _chan.get() }; | ||||
} | } | ||||
struct write_awaiter | |||||
struct [[nodiscard]] write_awaiter | |||||
{ | { | ||||
using state_type = typename channel_type::state_write_t; | using state_type = typename channel_type::state_write_t; | ||||
_channel->try_write(static_cast<value_type&>(_value)); | _channel->try_write(static_cast<value_type&>(_value)); | ||||
} | } | ||||
bool await_ready() | |||||
bool await_ready() const noexcept | |||||
{ | { | ||||
if (_channel->try_write(static_cast<value_type&>(_value))) | |||||
{ | |||||
_channel = nullptr; | |||||
return true; | |||||
} | |||||
return false; | return false; | ||||
} | } | ||||
template<class _PromiseT, typename = std::enable_if_t<is_promise_v<_PromiseT>>> | template<class _PromiseT, typename = std::enable_if_t<is_promise_v<_PromiseT>>> | ||||
bool await_suspend(coroutine_handle<_PromiseT> handler) | bool await_suspend(coroutine_handle<_PromiseT> handler) | ||||
{ | { | ||||
scoped_lock<lock_type> lock_(_channel->_lock); | |||||
if (_channel->try_write_nolock(static_cast<value_type&>(_value))) | |||||
{ | |||||
_channel = nullptr; | |||||
return false; | |||||
} | |||||
_state = new state_type(_channel, static_cast<value_type&>(_value)); | _state = new state_type(_channel, static_cast<value_type&>(_value)); | ||||
_state->on_await_suspend(handler); | |||||
_channel->add_write_list_nolock(_state.get()); | |||||
_channel = nullptr; | _channel = nullptr; | ||||
return _state->on_await_suspend(handler); | |||||
return true; | |||||
} | } | ||||
void await_resume() | void await_resume() | ||||
{ | { | ||||
std::shared_ptr<channel_type> _chan; | std::shared_ptr<channel_type> _chan; | ||||
}; | }; | ||||
//不支持channel_t<void> | |||||
template<> | |||||
struct channel_t<void> | |||||
{ | |||||
}; | |||||
using semaphore_t = channel_t<bool>; | using semaphore_t = channel_t<bool>; | ||||
#pragma once | #pragma once | ||||
#define LIB_RESUMEF_VERSION 20304 // 2.3.4 | |||||
#define LIB_RESUMEF_VERSION 20400 // 2.4.0 | |||||
#if defined(RESUMEF_MODULE_EXPORT) | #if defined(RESUMEF_MODULE_EXPORT) | ||||
#define RESUMEF_NS export namespace resumef | #define RESUMEF_NS export namespace resumef | ||||
struct scheduler_t; | struct scheduler_t; | ||||
template<class _Ty = void> | template<class _Ty = void> | ||||
struct future_t; | |||||
struct [[nodiscard]] future_t; | |||||
using future_vt [[deprecated]] = future_t<>; | using future_vt [[deprecated]] = future_t<>; | ||||
#pragma once | |||||
RESUMEF_NS | |||||
{ | |||||
template<class _Node, class _Sty = uint32_t> | |||||
struct intrusive_link_queue | |||||
{ | |||||
using node_type = _Node; | |||||
using size_type = _Sty; | |||||
public: | |||||
intrusive_link_queue(); | |||||
intrusive_link_queue(const intrusive_link_queue&) = delete; | |||||
intrusive_link_queue(intrusive_link_queue&&) = default; | |||||
intrusive_link_queue& operator =(const intrusive_link_queue&) = delete; | |||||
intrusive_link_queue& operator =(intrusive_link_queue&&) = default; | |||||
auto size() const noexcept->size_type; | |||||
bool empty() const noexcept; | |||||
void push_back(node_type* node) noexcept; | |||||
auto try_pop() noexcept->node_type*; | |||||
private: | |||||
node_type* _head; | |||||
node_type* _tail; | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
std::atomic<size_type> m_count; | |||||
#endif | |||||
}; | |||||
template<class _Node, class _Sty> | |||||
intrusive_link_queue<_Node, _Sty>::intrusive_link_queue() | |||||
: _head(nullptr) | |||||
, _tail(nullptr) | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
, m_count(0) | |||||
#endif | |||||
{ | |||||
} | |||||
template<class _Node, class _Sty> | |||||
auto intrusive_link_queue<_Node, _Sty>::size() const noexcept->size_type | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load(std::memory_order_acquire); | |||||
#else | |||||
size_type count = 0; | |||||
for (node_type* node = _head; node != nullptr; node = node->next) | |||||
++count; | |||||
return count; | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Node, class _Sty> | |||||
bool intrusive_link_queue<_Node, _Sty>::empty() const noexcept | |||||
{ | |||||
return _head == nullptr; | |||||
} | |||||
template<class _Node, class _Sty> | |||||
void intrusive_link_queue<_Node, _Sty>::push_back(node_type* node) noexcept | |||||
{ | |||||
assert(node != nullptr); | |||||
node->_next = nullptr; | |||||
if (_head == nullptr) | |||||
{ | |||||
_head = _tail = node; | |||||
} | |||||
else | |||||
{ | |||||
_tail->_next = node; | |||||
_tail = node; | |||||
} | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_add(1, std::memory_order_acq_rel); | |||||
#endif | |||||
} | |||||
template<class _Node, class _Sty> | |||||
auto intrusive_link_queue<_Node, _Sty>::try_pop() noexcept->node_type* | |||||
{ | |||||
if (_head == nullptr) | |||||
return nullptr; | |||||
node_type* node = _head; | |||||
_head = node->_next; | |||||
node->_next = nullptr; | |||||
if (_tail == node) | |||||
{ | |||||
assert(node->_next == nullptr); | |||||
_tail = nullptr; | |||||
} | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_sub(1, std::memory_order_acq_rel); | |||||
#endif | |||||
return node; | |||||
} | |||||
} |
#pragma once | #pragma once | ||||
#include <memory> | |||||
#include <atomic> | |||||
#include <cassert> | |||||
#include <optional> | |||||
#include "src/spinlock.h" | |||||
//使用自旋锁完成的线程安全的环形队列。 | |||||
//支持多个线程同时push和pop。 | |||||
//_Option : 如果队列保存的数据不支持拷贝只支持移动,则需要设置为true;或者数据希望pop后销毁,都需要设置为true。 | |||||
//_Sty : 内存保持数量和索引的整数类型。用于外部控制队列的结构体大小。 | |||||
template<class _Ty, bool _Option = false, class _Sty = uint32_t> | |||||
struct ring_queue | |||||
RESUMEF_NS | |||||
{ | { | ||||
using value_type = _Ty; | |||||
using size_type = _Sty; | |||||
static constexpr bool use_option = _Option; | |||||
using optional_type = std::conditional_t<use_option, std::optional<value_type>, value_type>; | |||||
public: | |||||
ring_queue(size_t sz); | |||||
ring_queue(const ring_queue&) = delete; | |||||
ring_queue(ring_queue&&) = default; | |||||
ring_queue& operator =(const ring_queue&) = delete; | |||||
ring_queue& operator =(ring_queue&&) = default; | |||||
auto size() const noexcept->size_type; | |||||
auto capacity() const noexcept->size_type; | |||||
bool empty() const noexcept; | |||||
bool full() const noexcept; | |||||
template<class U> | |||||
bool try_push(U&& value) noexcept(std::is_nothrow_move_assignable_v<U>); | |||||
bool try_pop(value_type& value) noexcept(std::is_nothrow_move_assignable_v<value_type>); | |||||
private: | |||||
using container_type = std::conditional_t<std::is_same_v<value_type, bool>, std::unique_ptr<optional_type[]>, std::vector<optional_type>>; | |||||
container_type m_bufferPtr; | |||||
size_type m_bufferSize; | |||||
size_type m_writeIndex; | |||||
size_type m_readIndex; | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
std::atomic<size_type> m_count; | |||||
#endif | |||||
auto nextIndex(size_type a_count) const noexcept->size_type; | |||||
}; | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
ring_queue<_Ty, _Option, _Sty>::ring_queue(size_t sz) | |||||
: m_bufferSize(static_cast<size_type>(sz + 1)) | |||||
, m_writeIndex(0) | |||||
, m_readIndex(0) | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
, m_count(0) | |||||
#endif | |||||
{ | |||||
if constexpr (std::is_same_v<value_type, bool>) | |||||
m_bufferPtr = container_type{ new optional_type[sz + 1] }; | |||||
else | |||||
m_bufferPtr.resize(sz + 1); | |||||
//使用自旋锁完成的线程安全的环形队列。 | |||||
//支持多个线程同时push和pop。 | |||||
//_Option : 如果队列保存的数据不支持拷贝只支持移动,则需要设置为true;或者数据希望pop后销毁,都需要设置为true。 | |||||
//_Sty : 内存保持数量和索引的整数类型。用于外部控制队列的结构体大小。 | |||||
template<class _Ty, bool _Option = false, class _Sty = uint32_t> | |||||
struct ring_queue | |||||
{ | |||||
using value_type = _Ty; | |||||
using size_type = _Sty; | |||||
static constexpr bool use_option = _Option; | |||||
using optional_type = std::conditional_t<use_option, std::optional<value_type>, value_type>; | |||||
public: | |||||
ring_queue(size_t sz); | |||||
ring_queue(const ring_queue&) = delete; | |||||
ring_queue(ring_queue&&) = default; | |||||
ring_queue& operator =(const ring_queue&) = delete; | |||||
ring_queue& operator =(ring_queue&&) = default; | |||||
auto size() const noexcept->size_type; | |||||
auto capacity() const noexcept->size_type; | |||||
bool empty() const noexcept; | |||||
bool full() const noexcept; | |||||
template<class U> | |||||
bool try_push(U&& value) noexcept(std::is_nothrow_move_assignable_v<U>); | |||||
bool try_pop(value_type& value) noexcept(std::is_nothrow_move_assignable_v<value_type>); | |||||
private: | |||||
using container_type = std::conditional_t<std::is_same_v<value_type, bool>, std::unique_ptr<optional_type[]>, std::vector<optional_type>>; | |||||
container_type m_bufferPtr; | |||||
size_type m_bufferSize; | |||||
size_type m_writeIndex; | |||||
size_type m_readIndex; | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
std::atomic<size_type> m_count; | |||||
#endif | |||||
auto nextIndex(size_type a_count) const noexcept->size_type; | |||||
}; | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
ring_queue<_Ty, _Option, _Sty>::ring_queue(size_t sz) | |||||
: m_bufferSize(static_cast<size_type>(sz + 1)) | |||||
, m_writeIndex(0) | |||||
, m_readIndex(0) | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
, m_count(0) | |||||
#endif | |||||
{ | |||||
if constexpr (std::is_same_v<value_type, bool>) | |||||
m_bufferPtr = container_type{ new optional_type[sz + 1] }; | |||||
else | |||||
m_bufferPtr.resize(sz + 1); | |||||
assert(sz < (std::numeric_limits<size_type>::max)()); | |||||
} | |||||
assert(sz < (std::numeric_limits<size_type>::max)()); | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue<_Ty, _Option, _Sty>::nextIndex(size_type a_count) const noexcept->size_type | |||||
{ | |||||
return static_cast<size_type>((a_count + 1) % m_bufferSize); | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue<_Ty, _Option, _Sty>::nextIndex(size_type a_count) const noexcept->size_type | |||||
{ | |||||
return static_cast<size_type>((a_count + 1) % m_bufferSize); | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue<_Ty, _Option, _Sty>::size() const noexcept->size_type | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load(std::memory_order_acquire); | |||||
#else | |||||
if (m_writeIndex >= m_readIndex) | |||||
return (m_writeIndex - m_readIndex); | |||||
else | |||||
return (m_bufferSize + m_writeIndex - m_readIndex); | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue<_Ty, _Option, _Sty>::capacity() const noexcept->size_type | |||||
{ | |||||
return m_bufferSize - 1; | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue<_Ty, _Option, _Sty>::size() const noexcept->size_type | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load(std::memory_order_acquire); | |||||
#else | |||||
if (m_writeIndex >= m_readIndex) | |||||
return (m_writeIndex - m_readIndex); | |||||
else | |||||
return (m_bufferSize + m_writeIndex - m_readIndex); | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue<_Ty, _Option, _Sty>::empty() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load(std::memory_order_acquire) == 0; | |||||
#else | |||||
return m_writeIndex == m_readIndex; | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue<_Ty, _Option, _Sty>::full() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return (m_count.load(std::memory_order_acquire) == (m_bufferSize - 1)); | |||||
#else | |||||
return nextIndex(m_writeIndex) == m_readIndex; | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
template<class U> | |||||
bool ring_queue<_Ty, _Option, _Sty>::try_push(U&& value) noexcept(std::is_nothrow_move_assignable_v<U>) | |||||
{ | |||||
auto nextWriteIndex = nextIndex(m_writeIndex); | |||||
if (nextWriteIndex == m_readIndex) | |||||
return false; | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue<_Ty, _Option, _Sty>::capacity() const noexcept->size_type | |||||
{ | |||||
return m_bufferSize - 1; | |||||
} | |||||
assert(m_writeIndex < m_bufferSize); | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue<_Ty, _Option, _Sty>::empty() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load(std::memory_order_acquire) == 0; | |||||
#else | |||||
return m_writeIndex == m_readIndex; | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue<_Ty, _Option, _Sty>::full() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return (m_count.load(std::memory_order_acquire) == (m_bufferSize - 1)); | |||||
#else | |||||
return nextIndex(m_writeIndex) == m_readIndex; | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
m_bufferPtr[m_writeIndex] = std::move(value); | |||||
m_writeIndex = nextWriteIndex; | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
template<class U> | |||||
bool ring_queue<_Ty, _Option, _Sty>::try_push(U&& value) noexcept(std::is_nothrow_move_assignable_v<U>) | |||||
{ | |||||
auto nextWriteIndex = nextIndex(m_writeIndex); | |||||
if (nextWriteIndex == m_readIndex) | |||||
return false; | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_add(1, std::memory_order_acq_rel); | |||||
#endif | |||||
return true; | |||||
} | |||||
assert(m_writeIndex < m_bufferSize); | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue<_Ty, _Option, _Sty>::try_pop(value_type& value) noexcept(std::is_nothrow_move_assignable_v<value_type>) | |||||
{ | |||||
if (m_readIndex == m_writeIndex) | |||||
return false; | |||||
m_bufferPtr[m_writeIndex] = std::move(value); | |||||
m_writeIndex = nextWriteIndex; | |||||
optional_type& ov = m_bufferPtr[m_readIndex]; | |||||
if constexpr (use_option) | |||||
{ | |||||
value = std::move(ov.value()); | |||||
ov = std::nullopt; | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_add(1, std::memory_order_acq_rel); | |||||
#endif | |||||
return true; | |||||
} | } | ||||
else | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue<_Ty, _Option, _Sty>::try_pop(value_type& value) noexcept(std::is_nothrow_move_assignable_v<value_type>) | |||||
{ | { | ||||
value = std::move(ov); | |||||
if (m_readIndex == m_writeIndex) | |||||
return false; | |||||
optional_type& ov = m_bufferPtr[m_readIndex]; | |||||
if constexpr (use_option) | |||||
{ | |||||
value = std::move(ov.value()); | |||||
ov = std::nullopt; | |||||
} | |||||
else | |||||
{ | |||||
value = std::move(ov); | |||||
} | |||||
m_readIndex = nextIndex(m_readIndex); | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_sub(1, std::memory_order_acq_rel); | |||||
#endif | |||||
return true; | |||||
} | } | ||||
m_readIndex = nextIndex(m_readIndex); | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_sub(1, std::memory_order_acq_rel); | |||||
#endif | |||||
return true; | |||||
} | |||||
} |
#pragma once | #pragma once | ||||
#include <memory> | |||||
#include <atomic> | |||||
#include <cassert> | |||||
#include <thread> | |||||
//目前无法解决三个索引数值回绕导致的问题 | |||||
//如果为了避免索引回绕的问题,索引采用uint64_t类型, | |||||
//则在与spinlock<T, false, uint32_t>版本的对比中速度反而慢了 | |||||
//pop时无法使用move语义来获取数据。因为算法要求先获取值,且获取后有可能失败,从而重新获取其它值。 | |||||
template<class _Ty, class _Sty = uint32_t> | |||||
struct ring_queue_lockfree | |||||
RESUMEF_NS | |||||
{ | { | ||||
using value_type = _Ty; | |||||
using size_type = _Sty; | |||||
public: | |||||
ring_queue_lockfree(size_t sz); | |||||
ring_queue_lockfree(const ring_queue_lockfree&) = delete; | |||||
ring_queue_lockfree(ring_queue_lockfree&&) = default; | |||||
ring_queue_lockfree& operator =(const ring_queue_lockfree&) = delete; | |||||
ring_queue_lockfree& operator =(ring_queue_lockfree&&) = default; | |||||
auto size() const noexcept->size_type; | |||||
auto capacity() const noexcept->size_type; | |||||
bool empty() const noexcept; | |||||
bool full() const noexcept; | |||||
template<class U> | |||||
bool try_push(U&& value) noexcept(std::is_nothrow_move_constructible_v<U>); | |||||
bool try_pop(value_type& value) noexcept(std::is_nothrow_move_constructible_v<value_type>); | |||||
private: | |||||
std::unique_ptr<value_type[]> m_bufferPtr; | |||||
size_type m_bufferSize; | |||||
std::atomic<size_type> m_writeIndex; //Where a new element will be inserted to. | |||||
std::atomic<size_type> m_readIndex; //Where the next element where be extracted from. | |||||
std::atomic<size_type> m_maximumReadIndex; //It points to the place where the latest "commited" data has been inserted. | |||||
//If it's not the same as writeIndex it means there are writes pending to be "commited" to the queue, | |||||
//that means that the place for the data was reserved (the index in the array) | |||||
//but the data is still not in the queue, | |||||
//so the thread trying to read will have to wait for those other threads to | |||||
//save the data into the queue. | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
std::atomic<size_type> m_count; | |||||
#endif | |||||
auto countToIndex(size_type a_count) const noexcept->size_type; | |||||
auto nextIndex(size_type a_count) const noexcept->size_type; | |||||
}; | |||||
template<class _Ty, class _Sty> | |||||
ring_queue_lockfree<_Ty, _Sty>::ring_queue_lockfree(size_t sz) | |||||
: m_bufferPtr(new value_type[sz + 1]) | |||||
, m_bufferSize(static_cast<size_type>(sz + 1)) | |||||
, m_writeIndex(0) | |||||
, m_readIndex(0) | |||||
, m_maximumReadIndex(0) | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
, m_count(0) | |||||
#endif | |||||
{ | |||||
assert(sz < (std::numeric_limits<size_type>::max)()); | |||||
} | |||||
template<class _Ty, class _Sty> | |||||
auto ring_queue_lockfree<_Ty, _Sty>::countToIndex(size_type a_count) const noexcept->size_type | |||||
{ | |||||
//return (a_count % m_bufferSize); | |||||
return a_count; | |||||
} | |||||
template<class _Ty, class _Sty> | |||||
auto ring_queue_lockfree<_Ty, _Sty>::nextIndex(size_type a_count) const noexcept->size_type | |||||
{ | |||||
//return static_cast<size_type>((a_count + 1)); | |||||
return static_cast<size_type>((a_count + 1) % m_bufferSize); | |||||
} | |||||
//目前无法解决三个索引数值回绕导致的问题 | |||||
//如果为了避免索引回绕的问题,索引采用uint64_t类型, | |||||
//则在与spinlock<T, false, uint32_t>版本的对比中速度反而慢了 | |||||
//pop时无法使用move语义来获取数据。因为算法要求先获取值,且获取后有可能失败,从而重新获取其它值。 | |||||
template<class _Ty, class _Sty = uint32_t> | |||||
struct ring_queue_lockfree | |||||
{ | |||||
using value_type = _Ty; | |||||
using size_type = _Sty; | |||||
public: | |||||
ring_queue_lockfree(size_t sz); | |||||
ring_queue_lockfree(const ring_queue_lockfree&) = delete; | |||||
ring_queue_lockfree(ring_queue_lockfree&&) = default; | |||||
ring_queue_lockfree& operator =(const ring_queue_lockfree&) = delete; | |||||
ring_queue_lockfree& operator =(ring_queue_lockfree&&) = default; | |||||
auto size() const noexcept->size_type; | |||||
auto capacity() const noexcept->size_type; | |||||
bool empty() const noexcept; | |||||
bool full() const noexcept; | |||||
template<class U> | |||||
bool try_push(U&& value) noexcept(std::is_nothrow_move_constructible_v<U>); | |||||
bool try_pop(value_type& value) noexcept(std::is_nothrow_move_constructible_v<value_type>); | |||||
private: | |||||
std::unique_ptr<value_type[]> m_bufferPtr; | |||||
size_type m_bufferSize; | |||||
std::atomic<size_type> m_writeIndex; //Where a new element will be inserted to. | |||||
std::atomic<size_type> m_readIndex; //Where the next element where be extracted from. | |||||
std::atomic<size_type> m_maximumReadIndex; //It points to the place where the latest "commited" data has been inserted. | |||||
//If it's not the same as writeIndex it means there are writes pending to be "commited" to the queue, | |||||
//that means that the place for the data was reserved (the index in the array) | |||||
//but the data is still not in the queue, | |||||
//so the thread trying to read will have to wait for those other threads to | |||||
//save the data into the queue. | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
std::atomic<size_type> m_count; | |||||
#endif | |||||
auto countToIndex(size_type a_count) const noexcept->size_type; | |||||
auto nextIndex(size_type a_count) const noexcept->size_type; | |||||
}; | |||||
template<class _Ty, class _Sty> | |||||
ring_queue_lockfree<_Ty, _Sty>::ring_queue_lockfree(size_t sz) | |||||
: m_bufferPtr(new value_type[sz + 1]) | |||||
, m_bufferSize(static_cast<size_type>(sz + 1)) | |||||
, m_writeIndex(0) | |||||
, m_readIndex(0) | |||||
, m_maximumReadIndex(0) | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
, m_count(0) | |||||
#endif | |||||
{ | |||||
assert(sz < (std::numeric_limits<size_type>::max)()); | |||||
} | |||||
template<class _Ty, class _Sty> | |||||
auto ring_queue_lockfree<_Ty, _Sty>::size() const noexcept->size_type | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load(); | |||||
#else | |||||
auto currentWriteIndex = m_maximumReadIndex.load(std::memory_order_acquire); | |||||
currentWriteIndex = countToIndex(currentWriteIndex); | |||||
auto currentReadIndex = m_readIndex.load(std::memory_order_acquire); | |||||
currentReadIndex = countToIndex(currentReadIndex); | |||||
if (currentWriteIndex >= currentReadIndex) | |||||
return (currentWriteIndex - currentReadIndex); | |||||
else | |||||
return (m_bufferSize + currentWriteIndex - currentReadIndex); | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, class _Sty> | |||||
auto ring_queue_lockfree<_Ty, _Sty>::capacity() const noexcept->size_type | |||||
{ | |||||
return m_bufferSize - 1; | |||||
} | |||||
template<class _Ty, class _Sty> | |||||
auto ring_queue_lockfree<_Ty, _Sty>::countToIndex(size_type a_count) const noexcept->size_type | |||||
{ | |||||
//return (a_count % m_bufferSize); | |||||
return a_count; | |||||
} | |||||
template<class _Ty, class _Sty> | |||||
bool ring_queue_lockfree<_Ty, _Sty>::empty() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load() == 0; | |||||
#else | |||||
auto currentWriteIndex = m_maximumReadIndex.load(std::memory_order_acquire); | |||||
auto currentReadIndex = m_readIndex.load(std::memory_order_acquire); | |||||
return countToIndex(currentWriteIndex) == countToIndex(currentReadIndex); | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, class _Sty> | |||||
bool ring_queue_lockfree<_Ty, _Sty>::full() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return (m_count.load() == (m_bufferSize - 1)); | |||||
#else | |||||
auto currentWriteIndex = m_writeIndex.load(std::memory_order_acquire); | |||||
auto currentReadIndex = m_readIndex.load(std::memory_order_acquire); | |||||
return countToIndex(nextIndex(currentWriteIndex)) == countToIndex(currentReadIndex); | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, class _Sty> | |||||
template<class U> | |||||
bool ring_queue_lockfree<_Ty, _Sty>::try_push(U&& value) noexcept(std::is_nothrow_move_constructible_v<U>) | |||||
{ | |||||
auto currentWriteIndex = m_writeIndex.load(std::memory_order_acquire); | |||||
template<class _Ty, class _Sty> | |||||
auto ring_queue_lockfree<_Ty, _Sty>::nextIndex(size_type a_count) const noexcept->size_type | |||||
{ | |||||
//return static_cast<size_type>((a_count + 1)); | |||||
return static_cast<size_type>((a_count + 1) % m_bufferSize); | |||||
} | |||||
do | |||||
template<class _Ty, class _Sty> | |||||
auto ring_queue_lockfree<_Ty, _Sty>::size() const noexcept->size_type | |||||
{ | { | ||||
if (countToIndex(nextIndex(currentWriteIndex)) == countToIndex(m_readIndex.load(std::memory_order_acquire))) | |||||
{ | |||||
// the queue is full | |||||
return false; | |||||
} | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load(); | |||||
#else | |||||
auto currentWriteIndex = m_maximumReadIndex.load(std::memory_order_acquire); | |||||
currentWriteIndex = countToIndex(currentWriteIndex); | |||||
auto currentReadIndex = m_readIndex.load(std::memory_order_acquire); | |||||
currentReadIndex = countToIndex(currentReadIndex); | |||||
if (currentWriteIndex >= currentReadIndex) | |||||
return (currentWriteIndex - currentReadIndex); | |||||
else | |||||
return (m_bufferSize + currentWriteIndex - currentReadIndex); | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
// There is more than one producer. Keep looping till this thread is able | |||||
// to allocate space for current piece of data | |||||
// | |||||
// using compare_exchange_strong because it isn't allowed to fail spuriously | |||||
// When the compare_exchange operation is in a loop the weak version | |||||
// will yield better performance on some platforms, but here we'd have to | |||||
// load m_writeIndex all over again | |||||
} while (!m_writeIndex.compare_exchange_strong(currentWriteIndex, nextIndex(currentWriteIndex), std::memory_order_acq_rel)); | |||||
// Just made sure this index is reserved for this thread. | |||||
m_bufferPtr[countToIndex(currentWriteIndex)] = std::move(value); | |||||
// update the maximum read index after saving the piece of data. It can't | |||||
// fail if there is only one thread inserting in the queue. It might fail | |||||
// if there is more than 1 producer thread because this operation has to | |||||
// be done in the same order as the previous CAS | |||||
// | |||||
// using compare_exchange_weak because they are allowed to fail spuriously | |||||
// (act as if *this != expected, even if they are equal), but when the | |||||
// compare_exchange operation is in a loop the weak version will yield | |||||
// better performance on some platforms. | |||||
auto savedWriteIndex = currentWriteIndex; | |||||
while (!m_maximumReadIndex.compare_exchange_weak(currentWriteIndex, nextIndex(currentWriteIndex), std::memory_order_acq_rel)) | |||||
template<class _Ty, class _Sty> | |||||
auto ring_queue_lockfree<_Ty, _Sty>::capacity() const noexcept->size_type | |||||
{ | { | ||||
currentWriteIndex = savedWriteIndex; | |||||
// this is a good place to yield the thread in case there are more | |||||
// software threads than hardware processors and you have more | |||||
// than 1 producer thread | |||||
// have a look at sched_yield (POSIX.1b) | |||||
std::this_thread::yield(); | |||||
return m_bufferSize - 1; | |||||
} | } | ||||
// The value was successfully inserted into the queue | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_add(1); | |||||
#endif | |||||
return true; | |||||
} | |||||
template<class _Ty, class _Sty> | |||||
bool ring_queue_lockfree<_Ty, _Sty>::empty() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load() == 0; | |||||
#else | |||||
auto currentWriteIndex = m_maximumReadIndex.load(std::memory_order_acquire); | |||||
auto currentReadIndex = m_readIndex.load(std::memory_order_acquire); | |||||
return countToIndex(currentWriteIndex) == countToIndex(currentReadIndex); | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, class _Sty> | |||||
bool ring_queue_lockfree<_Ty, _Sty>::try_pop(value_type & value) noexcept(std::is_nothrow_move_constructible_v<value_type>) | |||||
{ | |||||
auto currentReadIndex = m_readIndex.load(std::memory_order_acquire); | |||||
template<class _Ty, class _Sty> | |||||
bool ring_queue_lockfree<_Ty, _Sty>::full() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return (m_count.load() == (m_bufferSize - 1)); | |||||
#else | |||||
auto currentWriteIndex = m_writeIndex.load(std::memory_order_acquire); | |||||
auto currentReadIndex = m_readIndex.load(std::memory_order_acquire); | |||||
return countToIndex(nextIndex(currentWriteIndex)) == countToIndex(currentReadIndex); | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
for(;;) | |||||
template<class _Ty, class _Sty> | |||||
template<class U> | |||||
bool ring_queue_lockfree<_Ty, _Sty>::try_push(U&& value) noexcept(std::is_nothrow_move_constructible_v<U>) | |||||
{ | { | ||||
auto idx = countToIndex(currentReadIndex); | |||||
auto currentWriteIndex = m_writeIndex.load(std::memory_order_acquire); | |||||
// to ensure thread-safety when there is more than 1 producer | |||||
// thread a second index is defined (m_maximumReadIndex) | |||||
if (idx == countToIndex(m_maximumReadIndex.load(std::memory_order_acquire))) | |||||
do | |||||
{ | |||||
if (countToIndex(nextIndex(currentWriteIndex)) == countToIndex(m_readIndex.load(std::memory_order_acquire))) | |||||
{ | |||||
// the queue is full | |||||
return false; | |||||
} | |||||
// There is more than one producer. Keep looping till this thread is able | |||||
// to allocate space for current piece of data | |||||
// | |||||
// using compare_exchange_strong because it isn't allowed to fail spuriously | |||||
// When the compare_exchange operation is in a loop the weak version | |||||
// will yield better performance on some platforms, but here we'd have to | |||||
// load m_writeIndex all over again | |||||
} while (!m_writeIndex.compare_exchange_strong(currentWriteIndex, nextIndex(currentWriteIndex), std::memory_order_acq_rel)); | |||||
// Just made sure this index is reserved for this thread. | |||||
m_bufferPtr[countToIndex(currentWriteIndex)] = std::move(value); | |||||
// update the maximum read index after saving the piece of data. It can't | |||||
// fail if there is only one thread inserting in the queue. It might fail | |||||
// if there is more than 1 producer thread because this operation has to | |||||
// be done in the same order as the previous CAS | |||||
// | |||||
// using compare_exchange_weak because they are allowed to fail spuriously | |||||
// (act as if *this != expected, even if they are equal), but when the | |||||
// compare_exchange operation is in a loop the weak version will yield | |||||
// better performance on some platforms. | |||||
auto savedWriteIndex = currentWriteIndex; | |||||
while (!m_maximumReadIndex.compare_exchange_weak(currentWriteIndex, nextIndex(currentWriteIndex), std::memory_order_acq_rel)) | |||||
{ | { | ||||
// the queue is empty or | |||||
// a producer thread has allocate space in the queue but is | |||||
// waiting to commit the data into it | |||||
return false; | |||||
currentWriteIndex = savedWriteIndex; | |||||
// this is a good place to yield the thread in case there are more | |||||
// software threads than hardware processors and you have more | |||||
// than 1 producer thread | |||||
// have a look at sched_yield (POSIX.1b) | |||||
std::this_thread::yield(); | |||||
} | } | ||||
// retrieve the data from the queue | |||||
value = m_bufferPtr[idx]; //但是,这里的方法不适合。如果只支持移动怎么办? | |||||
// The value was successfully inserted into the queue | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_add(1); | |||||
#endif | |||||
return true; | |||||
} | |||||
// try to perfrom now the CAS operation on the read index. If we succeed | |||||
// a_data already contains what m_readIndex pointed to before we | |||||
// increased it | |||||
if (m_readIndex.compare_exchange_strong(currentReadIndex, nextIndex(currentReadIndex), std::memory_order_acq_rel)) | |||||
{ | |||||
// got here. The value was retrieved from the queue. Note that the | |||||
// data inside the m_queue array is not deleted nor reseted | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_sub(1); | |||||
#endif | |||||
return true; | |||||
} | |||||
template<class _Ty, class _Sty> | |||||
bool ring_queue_lockfree<_Ty, _Sty>::try_pop(value_type& value) noexcept(std::is_nothrow_move_constructible_v<value_type>) | |||||
{ | |||||
auto currentReadIndex = m_readIndex.load(std::memory_order_acquire); | |||||
// it failed retrieving the element off the queue. Someone else must | |||||
// have read the element stored at countToIndex(currentReadIndex) | |||||
// before we could perform the CAS operation | |||||
} // keep looping to try again! | |||||
} | |||||
for (;;) | |||||
{ | |||||
auto idx = countToIndex(currentReadIndex); | |||||
// to ensure thread-safety when there is more than 1 producer | |||||
// thread a second index is defined (m_maximumReadIndex) | |||||
if (idx == countToIndex(m_maximumReadIndex.load(std::memory_order_acquire))) | |||||
{ | |||||
// the queue is empty or | |||||
// a producer thread has allocate space in the queue but is | |||||
// waiting to commit the data into it | |||||
return false; | |||||
} | |||||
// retrieve the data from the queue | |||||
value = m_bufferPtr[idx]; //但是,这里的方法不适合。如果只支持移动怎么办? | |||||
// try to perfrom now the CAS operation on the read index. If we succeed | |||||
// a_data already contains what m_readIndex pointed to before we | |||||
// increased it | |||||
if (m_readIndex.compare_exchange_strong(currentReadIndex, nextIndex(currentReadIndex), std::memory_order_acq_rel)) | |||||
{ | |||||
// got here. The value was retrieved from the queue. Note that the | |||||
// data inside the m_queue array is not deleted nor reseted | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_sub(1); | |||||
#endif | |||||
return true; | |||||
} | |||||
// it failed retrieving the element off the queue. Someone else must | |||||
// have read the element stored at countToIndex(currentReadIndex) | |||||
// before we could perform the CAS operation | |||||
} // keep looping to try again! | |||||
} | |||||
} |
#pragma once | #pragma once | ||||
#include <memory> | |||||
#include <atomic> | |||||
#include <cassert> | |||||
#include <optional> | |||||
#include "src/spinlock.h" | |||||
//使用自旋锁完成的线程安全的环形队列。 | |||||
//支持多个线程同时push和pop。 | |||||
//_Option : 如果队列保存的数据不支持拷贝只支持移动,则需要设置为true;或者数据希望pop后销毁,都需要设置为true。 | |||||
//_Sty : 内存保持数量和索引的整数类型。用于外部控制队列的结构体大小。 | |||||
template<class _Ty, bool _Option = false, class _Sty = uint32_t> | |||||
struct ring_queue_spinlock | |||||
RESUMEF_NS | |||||
{ | { | ||||
using value_type = _Ty; | |||||
using size_type = _Sty; | |||||
static constexpr bool use_option = _Option; | |||||
using optional_type = std::conditional_t<use_option, std::optional<value_type>, value_type>; | |||||
public: | |||||
ring_queue_spinlock(size_t sz); | |||||
ring_queue_spinlock(const ring_queue_spinlock&) = delete; | |||||
ring_queue_spinlock(ring_queue_spinlock&&) = default; | |||||
ring_queue_spinlock& operator =(const ring_queue_spinlock&) = delete; | |||||
ring_queue_spinlock& operator =(ring_queue_spinlock&&) = default; | |||||
auto size() const noexcept->size_type; | |||||
auto capacity() const noexcept->size_type; | |||||
bool empty() const noexcept; | |||||
bool full() const noexcept; | |||||
template<class U> | |||||
bool try_push(U&& value) noexcept(std::is_nothrow_move_assignable_v<U>); | |||||
bool try_pop(value_type& value) noexcept(std::is_nothrow_move_assignable_v<value_type>); | |||||
private: | |||||
using container_type = std::conditional_t<std::is_same_v<value_type, bool>, std::unique_ptr<optional_type[]>, std::vector<optional_type>>; | |||||
container_type m_bufferPtr; | |||||
size_type m_bufferSize; | |||||
size_type m_writeIndex; | |||||
size_type m_readIndex; | |||||
mutable resumef::spinlock m_lock; | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
std::atomic<size_type> m_count; | |||||
#endif | |||||
auto nextIndex(size_type a_count) const noexcept->size_type; | |||||
}; | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
ring_queue_spinlock<_Ty, _Option, _Sty>::ring_queue_spinlock(size_t sz) | |||||
: m_bufferSize(static_cast<size_type>(sz + 1)) | |||||
, m_writeIndex(0) | |||||
, m_readIndex(0) | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
, m_count(0) | |||||
#endif | |||||
{ | |||||
if constexpr (std::is_same_v<value_type, bool>) | |||||
m_bufferPtr = container_type{ new optional_type[sz + 1] }; | |||||
else | |||||
m_bufferPtr.resize(sz + 1); | |||||
//使用自旋锁完成的线程安全的环形队列。 | |||||
//支持多个线程同时push和pop。 | |||||
//_Option : 如果队列保存的数据不支持拷贝只支持移动,则需要设置为true;或者数据希望pop后销毁,都需要设置为true。 | |||||
//_Sty : 内存保持数量和索引的整数类型。用于外部控制队列的结构体大小。 | |||||
template<class _Ty, bool _Option = false, class _Sty = uint32_t> | |||||
struct ring_queue_spinlock | |||||
{ | |||||
using value_type = _Ty; | |||||
using size_type = _Sty; | |||||
static constexpr bool use_option = _Option; | |||||
using optional_type = std::conditional_t<use_option, std::optional<value_type>, value_type>; | |||||
public: | |||||
ring_queue_spinlock(size_t sz); | |||||
ring_queue_spinlock(const ring_queue_spinlock&) = delete; | |||||
ring_queue_spinlock(ring_queue_spinlock&&) = default; | |||||
ring_queue_spinlock& operator =(const ring_queue_spinlock&) = delete; | |||||
ring_queue_spinlock& operator =(ring_queue_spinlock&&) = default; | |||||
auto size() const noexcept->size_type; | |||||
auto capacity() const noexcept->size_type; | |||||
bool empty() const noexcept; | |||||
bool full() const noexcept; | |||||
template<class U> | |||||
bool try_push(U&& value) noexcept(std::is_nothrow_move_assignable_v<U>); | |||||
bool try_pop(value_type& value) noexcept(std::is_nothrow_move_assignable_v<value_type>); | |||||
private: | |||||
using container_type = std::conditional_t<std::is_same_v<value_type, bool>, std::unique_ptr<optional_type[]>, std::vector<optional_type>>; | |||||
container_type m_bufferPtr; | |||||
size_type m_bufferSize; | |||||
size_type m_writeIndex; | |||||
size_type m_readIndex; | |||||
mutable resumef::spinlock m_lock; | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
std::atomic<size_type> m_count; | |||||
#endif | |||||
auto nextIndex(size_type a_count) const noexcept->size_type; | |||||
}; | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
ring_queue_spinlock<_Ty, _Option, _Sty>::ring_queue_spinlock(size_t sz) | |||||
: m_bufferSize(static_cast<size_type>(sz + 1)) | |||||
, m_writeIndex(0) | |||||
, m_readIndex(0) | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
, m_count(0) | |||||
#endif | |||||
{ | |||||
if constexpr (std::is_same_v<value_type, bool>) | |||||
m_bufferPtr = container_type{ new optional_type[sz + 1] }; | |||||
else | |||||
m_bufferPtr.resize(sz + 1); | |||||
assert(sz < (std::numeric_limits<size_type>::max)()); | |||||
} | |||||
assert(sz < (std::numeric_limits<size_type>::max)()); | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue_spinlock<_Ty, _Option, _Sty>::nextIndex(size_type a_count) const noexcept->size_type | |||||
{ | |||||
return static_cast<size_type>((a_count + 1) % m_bufferSize); | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue_spinlock<_Ty, _Option, _Sty>::nextIndex(size_type a_count) const noexcept->size_type | |||||
{ | |||||
return static_cast<size_type>((a_count + 1) % m_bufferSize); | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue_spinlock<_Ty, _Option, _Sty>::size() const noexcept->size_type | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load(std::memory_order_acquire); | |||||
#else | |||||
std::scoped_lock __guard(this->m_lock); | |||||
if (m_writeIndex >= m_readIndex) | |||||
return (m_writeIndex - m_readIndex); | |||||
else | |||||
return (m_bufferSize + m_writeIndex - m_readIndex); | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue_spinlock<_Ty, _Option, _Sty>::capacity() const noexcept->size_type | |||||
{ | |||||
return m_bufferSize - 1; | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue_spinlock<_Ty, _Option, _Sty>::size() const noexcept->size_type | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load(std::memory_order_acquire); | |||||
#else | |||||
std::scoped_lock __guard(this->m_lock); | |||||
if (m_writeIndex >= m_readIndex) | |||||
return (m_writeIndex - m_readIndex); | |||||
else | |||||
return (m_bufferSize + m_writeIndex - m_readIndex); | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue_spinlock<_Ty, _Option, _Sty>::empty() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load(std::memory_order_acquire) == 0; | |||||
#else | |||||
std::scoped_lock __guard(this->m_lock); | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
auto ring_queue_spinlock<_Ty, _Option, _Sty>::capacity() const noexcept->size_type | |||||
{ | |||||
return m_bufferSize - 1; | |||||
} | |||||
return m_writeIndex == m_readIndex; | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue_spinlock<_Ty, _Option, _Sty>::empty() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return m_count.load(std::memory_order_acquire) == 0; | |||||
#else | |||||
std::scoped_lock __guard(this->m_lock); | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue_spinlock<_Ty, _Option, _Sty>::full() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return (m_count.load(std::memory_order_acquire) == (m_bufferSize - 1)); | |||||
#else | |||||
std::scoped_lock __guard(this->m_lock); | |||||
return nextIndex(m_writeIndex) == m_readIndex; | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
template<class U> | |||||
bool ring_queue_spinlock<_Ty, _Option, _Sty>::try_push(U&& value) noexcept(std::is_nothrow_move_assignable_v<U>) | |||||
{ | |||||
std::scoped_lock __guard(this->m_lock); | |||||
return m_writeIndex == m_readIndex; | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
auto nextWriteIndex = nextIndex(m_writeIndex); | |||||
if (nextWriteIndex == m_readIndex) | |||||
return false; | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue_spinlock<_Ty, _Option, _Sty>::full() const noexcept | |||||
{ | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
return (m_count.load(std::memory_order_acquire) == (m_bufferSize - 1)); | |||||
#else | |||||
std::scoped_lock __guard(this->m_lock); | |||||
assert(m_writeIndex < m_bufferSize); | |||||
return nextIndex(m_writeIndex) == m_readIndex; | |||||
#endif // _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
} | |||||
m_bufferPtr[m_writeIndex] = std::move(value); | |||||
m_writeIndex = nextWriteIndex; | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
template<class U> | |||||
bool ring_queue_spinlock<_Ty, _Option, _Sty>::try_push(U&& value) noexcept(std::is_nothrow_move_assignable_v<U>) | |||||
{ | |||||
std::scoped_lock __guard(this->m_lock); | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_add(1, std::memory_order_acq_rel); | |||||
#endif | |||||
return true; | |||||
} | |||||
auto nextWriteIndex = nextIndex(m_writeIndex); | |||||
if (nextWriteIndex == m_readIndex) | |||||
return false; | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue_spinlock<_Ty, _Option, _Sty>::try_pop(value_type& value) noexcept(std::is_nothrow_move_assignable_v<value_type>) | |||||
{ | |||||
std::scoped_lock __guard(this->m_lock); | |||||
assert(m_writeIndex < m_bufferSize); | |||||
if (m_readIndex == m_writeIndex) | |||||
return false; | |||||
m_bufferPtr[m_writeIndex] = std::move(value); | |||||
m_writeIndex = nextWriteIndex; | |||||
optional_type& ov = m_bufferPtr[m_readIndex]; | |||||
if constexpr (use_option) | |||||
{ | |||||
value = std::move(ov.value()); | |||||
ov = std::nullopt; | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_add(1, std::memory_order_acq_rel); | |||||
#endif | |||||
return true; | |||||
} | } | ||||
else | |||||
template<class _Ty, bool _Option, class _Sty> | |||||
bool ring_queue_spinlock<_Ty, _Option, _Sty>::try_pop(value_type& value) noexcept(std::is_nothrow_move_assignable_v<value_type>) | |||||
{ | { | ||||
value = std::move(ov); | |||||
std::scoped_lock __guard(this->m_lock); | |||||
if (m_readIndex == m_writeIndex) | |||||
return false; | |||||
optional_type& ov = m_bufferPtr[m_readIndex]; | |||||
if constexpr (use_option) | |||||
{ | |||||
value = std::move(ov.value()); | |||||
ov = std::nullopt; | |||||
} | |||||
else | |||||
{ | |||||
value = std::move(ov); | |||||
} | |||||
m_readIndex = nextIndex(m_readIndex); | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_sub(1, std::memory_order_acq_rel); | |||||
#endif | |||||
return true; | |||||
} | } | ||||
m_readIndex = nextIndex(m_readIndex); | |||||
#ifdef _WITH_LOCK_FREE_Q_KEEP_REAL_SIZE | |||||
m_count.fetch_sub(1, std::memory_order_acq_rel); | |||||
#endif | |||||
return true; | |||||
} | |||||
} |
{ | { | ||||
struct spinlock | struct spinlock | ||||
{ | { | ||||
static const size_t MAX_ACTIVE_SPIN = 4000; | |||||
static const size_t MAX_ACTIVE_SPIN = 1000; | |||||
static const size_t MAX_YIELD_SPIN = 4000; | |||||
static const int FREE_VALUE = 0; | static const int FREE_VALUE = 0; | ||||
static const int LOCKED_VALUE = 1; | static const int LOCKED_VALUE = 1; | ||||
void lock() noexcept | void lock() noexcept | ||||
{ | { | ||||
using namespace std::chrono; | |||||
int val = FREE_VALUE; | int val = FREE_VALUE; | ||||
if (!lck.compare_exchange_weak(val, LOCKED_VALUE, std::memory_order_acquire)) | |||||
if (!lck.compare_exchange_weak(val, LOCKED_VALUE, std::memory_order_acq_rel)) | |||||
{ | { | ||||
#if _DEBUG | #if _DEBUG | ||||
//诊断错误的用法:进行了递归锁调用 | |||||
assert(owner_thread_id != std::this_thread::get_id()); | assert(owner_thread_id != std::this_thread::get_id()); | ||||
#endif | #endif | ||||
size_t spinCount = 0; | size_t spinCount = 0; | ||||
auto dt = 1ms; | |||||
auto dt = std::chrono::milliseconds{ 1 }; | |||||
do | do | ||||
{ | { | ||||
while (lck.load(std::memory_order_relaxed) != FREE_VALUE) | while (lck.load(std::memory_order_relaxed) != FREE_VALUE) | ||||
{ | { | ||||
if (spinCount < MAX_ACTIVE_SPIN) | if (spinCount < MAX_ACTIVE_SPIN) | ||||
{ | |||||
++spinCount; | |||||
} | |||||
else if (spinCount < MAX_YIELD_SPIN) | |||||
{ | |||||
++spinCount; | ++spinCount; | ||||
std::this_thread::yield(); | |||||
} | |||||
else | else | ||||
{ | { | ||||
std::this_thread::sleep_for(dt); | std::this_thread::sleep_for(dt); | ||||
} | } | ||||
val = FREE_VALUE; | val = FREE_VALUE; | ||||
} while (!lck.compare_exchange_weak(val, LOCKED_VALUE, std::memory_order_acquire)); | |||||
} while (!lck.compare_exchange_weak(val, LOCKED_VALUE, std::memory_order_acq_rel)); | |||||
} | } | ||||
#if _DEBUG | #if _DEBUG | ||||
bool try_lock() noexcept | bool try_lock() noexcept | ||||
{ | { | ||||
int val = FREE_VALUE; | int val = FREE_VALUE; | ||||
bool ret = lck.compare_exchange_weak(val, LOCKED_VALUE, std::memory_order_acquire); | |||||
bool ret = lck.compare_exchange_weak(val, LOCKED_VALUE, std::memory_order_acq_rel); | |||||
#if _DEBUG | #if _DEBUG | ||||
if (ret) owner_thread_id = std::this_thread::get_id(); | if (ret) owner_thread_id = std::this_thread::get_id(); |
} | } | ||||
} | } | ||||
const size_t THREAD = 12; | |||||
const size_t BATCH = 10000; | |||||
const size_t MAX_CHANNEL_QUEUE = THREAD + 1; //0, 1, 5, 10, -1 | |||||
const size_t WRITE_THREAD = 6; | |||||
const size_t READ_THREAD = 6; | |||||
const size_t READ_BATCH = 1000000; | |||||
const size_t MAX_CHANNEL_QUEUE = 5; //0, 1, 5, 10, -1 | |||||
void resumable_main_channel_mult_thread() | void resumable_main_channel_mult_thread() | ||||
{ | { | ||||
channel_t<std::string> c(MAX_CHANNEL_QUEUE); | channel_t<std::string> c(MAX_CHANNEL_QUEUE); | ||||
std::thread write_th([&] | |||||
std::thread write_th[WRITE_THREAD]; | |||||
for (size_t i = 0; i < WRITE_THREAD; ++i) | |||||
{ | { | ||||
local_scheduler my_scheduler; //2017/12/14日,仍然存在BUG。真多线程下调度,存在有协程无法被调度完成的BUG | |||||
go test_channel_producer(c, BATCH * THREAD); | |||||
write_th[i] = std::thread([&] | |||||
{ | |||||
local_scheduler my_scheduler; | |||||
go test_channel_producer(c, READ_BATCH * READ_THREAD / WRITE_THREAD); | |||||
#if RESUMEF_ENABLE_MULT_SCHEDULER | #if RESUMEF_ENABLE_MULT_SCHEDULER | ||||
this_scheduler()->run_until_notask(); | |||||
this_scheduler()->run_until_notask(); | |||||
#endif | #endif | ||||
{ | |||||
scoped_lock<std::mutex> __lock(cout_mutex); | |||||
std::cout << "Write OK\r\n"; | |||||
} | |||||
}); | |||||
{ | |||||
scoped_lock<std::mutex> __lock(cout_mutex); | |||||
std::cout << "Write OK\r\n"; | |||||
} | |||||
}); | |||||
} | |||||
std::this_thread::sleep_for(100ms); | std::this_thread::sleep_for(100ms); | ||||
std::thread read_th[THREAD]; | |||||
for (size_t i = 0; i < THREAD; ++i) | |||||
std::thread read_th[READ_THREAD]; | |||||
for (size_t i = 0; i < READ_THREAD; ++i) | |||||
{ | { | ||||
read_th[i] = std::thread([&] | read_th[i] = std::thread([&] | ||||
{ | { | ||||
local_scheduler my_scheduler; //2017/12/14日,仍然存在BUG。真多线程下调度,存在有协程无法被调度完成的BUG | |||||
go test_channel_consumer(c, BATCH); | |||||
local_scheduler my_scheduler; | |||||
go test_channel_consumer(c, READ_BATCH); | |||||
#if RESUMEF_ENABLE_MULT_SCHEDULER | #if RESUMEF_ENABLE_MULT_SCHEDULER | ||||
this_scheduler()->run_until_notask(); | this_scheduler()->run_until_notask(); | ||||
#endif | #endif | ||||
std::this_thread::sleep_for(100ms); | std::this_thread::sleep_for(100ms); | ||||
scheduler_t::g_scheduler.run_until_notask(); | scheduler_t::g_scheduler.run_until_notask(); | ||||
#endif | #endif | ||||
for(auto & th : read_th) | for(auto & th : read_th) | ||||
th.join(); | th.join(); | ||||
write_th.join(); | |||||
for (auto& th : write_th) | |||||
th.join(); | |||||
std::cout << "OK: counter = " << gcounter.load() << std::endl; | std::cout << "OK: counter = " << gcounter.load() << std::endl; | ||||
} | } |
(void)argc; | (void)argc; | ||||
(void)argv; | (void)argv; | ||||
//test_ring_queue_simple<ring_queue_single_thread<int>>(); | |||||
//test_ring_queue<ring_queue_spinlock<int, false, uint32_t>>(); | |||||
//test_ring_queue<ring_queue_lockfree<int, uint64_t>>(); | |||||
//test_ring_queue_simple<resumef::ring_queue<int>>(); | |||||
//test_ring_queue<resumef::ring_queue_spinlock<int, false, uint32_t>>(); | |||||
//test_ring_queue<resumef::ring_queue_lockfree<int, uint64_t>>(); | |||||
resumable_main_channel(); | resumable_main_channel(); | ||||
resumable_main_channel_mult_thread(); | resumable_main_channel_mult_thread(); |
</PropertyGroup> | </PropertyGroup> | ||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> | ||||
<ConfigurationType>Application</ConfigurationType> | <ConfigurationType>Application</ConfigurationType> | ||||
<PlatformToolset>ClangCL</PlatformToolset> | |||||
<PlatformToolset>v142</PlatformToolset> | |||||
<UseDebugLibraries>true</UseDebugLibraries> | <UseDebugLibraries>true</UseDebugLibraries> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> | <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> | ||||
<ConfigurationType>Application</ConfigurationType> | <ConfigurationType>Application</ConfigurationType> | ||||
<UseDebugLibraries>false</UseDebugLibraries> | <UseDebugLibraries>false</UseDebugLibraries> | ||||
<PlatformToolset>ClangCL</PlatformToolset> | |||||
<PlatformToolset>v142</PlatformToolset> | |||||
<WholeProgramOptimization>true</WholeProgramOptimization> | <WholeProgramOptimization>true</WholeProgramOptimization> | ||||
<CharacterSet>NotSet</CharacterSet> | <CharacterSet>NotSet</CharacterSet> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<ClInclude Include="..\librf\src\event_v2.h" /> | <ClInclude Include="..\librf\src\event_v2.h" /> | ||||
<ClInclude Include="..\librf\src\future.h" /> | <ClInclude Include="..\librf\src\future.h" /> | ||||
<ClInclude Include="..\librf\src\generator.h" /> | <ClInclude Include="..\librf\src\generator.h" /> | ||||
<ClInclude Include="..\librf\src\intrusive_link_queue.h" /> | |||||
<ClInclude Include="..\librf\src\promise.h" /> | <ClInclude Include="..\librf\src\promise.h" /> | ||||
<ClInclude Include="..\librf\src\mutex.h" /> | <ClInclude Include="..\librf\src\mutex.h" /> | ||||
<ClInclude Include="..\librf\src\rf_task.h" /> | <ClInclude Include="..\librf\src\rf_task.h" /> |
<ClInclude Include="..\librf\src\ring_queue.h"> | <ClInclude Include="..\librf\src\ring_queue.h"> | ||||
<Filter>librf\src</Filter> | <Filter>librf\src</Filter> | ||||
</ClInclude> | </ClInclude> | ||||
<ClInclude Include="..\librf\src\intrusive_link_queue.h"> | |||||
<Filter>librf\src</Filter> | |||||
</ClInclude> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<None Include="..\librf\src\asio_task_1.12.0.inl"> | <None Include="..\librf\src\asio_task_1.12.0.inl"> |