Commit f074f70a authored by Xavier Thompson's avatar Xavier Thompson

mutex.hpp: Change the mutex implementation

parent d70c7609
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
#include <atomic> #include <atomic>
#include <coroutine> #include <coroutine>
#include <cstdint>
#include <typon/scheduler.hpp> #include <typon/scheduler.hpp>
#include <typon/stack.hpp> #include <typon/stack.hpp>
...@@ -13,42 +14,45 @@ namespace typon ...@@ -13,42 +14,45 @@ namespace typon
/* An asynchronous mutex. /* An asynchronous mutex.
Based on the MCS lock, but without the spinning. This means it should be Inspired by the implementation in CppCoro by Lewis Baker.
easy to implement spinning before suspension. https://github.com/lewissbaker/cppcoro
Disadvantage: each lock acquisition incurs an allocation. The alternative
is to potentially spin when unlocking.
*/ */
struct Mutex struct Mutex
{ {
struct Node struct Node
{ {
std::atomic<Node *> _next { nullptr }; Node * _next;
Stack * _stack; std::atomic<std::uintptr_t> _stack { 0 };
}; };
std::atomic<Node *> _state { nullptr }; std::atomic<Node *> _state { nullptr };
Node * _waiters { nullptr };
[[nodiscard]] auto lock() noexcept [[nodiscard]] auto lock() noexcept
{ {
struct awaitable struct awaitable : Node
{ {
std::atomic<Node *> & _state; Mutex * _mutex;
Node * _prev;
Node * _node;
bool await_ready() noexcept bool await_ready() noexcept
{ {
return !_prev; Node * next = nullptr;
for(;;)
{
// _next must be properly set before updating _state
_next = next;
if(_mutex->_state.compare_exchange_weak(next, this))
{
return !next;
}
}
} }
void await_suspend(std::coroutine_handle<> coroutine) noexcept void await_suspend(std::coroutine_handle<> coroutine) noexcept
{ {
auto stack = _node->_stack = Scheduler::suspend(coroutine); auto stack = Scheduler::suspend(coroutine);
auto ready = _prev->_next.exchange(_node); if (_stack.exchange(reinterpret_cast<std::uintptr_t>(stack)))
if (ready)
{ {
delete _prev;
Scheduler::enable(stack); Scheduler::enable(stack);
} }
} }
...@@ -57,28 +61,33 @@ namespace typon ...@@ -57,28 +61,33 @@ namespace typon
~awaitable() ~awaitable()
{ {
Node * node = _node; Node * waiter = _mutex->_waiters;
bool waiters = !_state.compare_exchange_strong(node, nullptr); if (!waiter)
if (waiters) {
Node * state = this;
if (_mutex->_state.compare_exchange_strong(state, nullptr))
{ {
node = _node; return;
auto next = node->_next.exchange(_node); }
if (next) auto next = state;
while (next != this)
{ {
delete node; auto tmp = next->_next;
Scheduler::enable(next->_stack); next->_next = waiter;
waiter = next;
next = tmp;
} }
} }
else _mutex->_waiters = waiter->_next;
auto stack = waiter->_stack.exchange(1);
if (stack)
{ {
delete node; Scheduler::enable(reinterpret_cast<Stack *>(stack));
} }
} }
}; };
auto node = new Node(); return awaitable { {}, this };
auto prev = _state.exchange(node);
return awaitable { this->_state, prev, node };
} }
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment