У меня есть реализация блокировки блокировки:
class Spinlock {
public:
void Lock() {
while (true) {
if (!_lock.test_and_set(std::memory_order_acquire)) {
return;
}
}
}
void Unlock() {
_lock.clear(std::memory_order_release);
}
private:
std::atomic_flag _lock;
};
Я использую класс SpinLock в:
class SpinlockedStack {
public:
SpinlockedStack() : _head(nullptr) {
}
~SpinlockedStack() {
while (_head != nullptr) {
Node* node = _head->Next;
delete _head;
_head = node;
}
}
void Push(int value) {
_lock.Lock();
_head = new Node(value, _head);
_lock.Unlock();
}
bool TryPop(int& value) {
_lock.Lock();
if (_head == nullptr) {
value = NULL;
_lock.Unlock();
return false;
}
Node* node = _head;
value = node->Value;
_head = node->Next;
delete node;
_lock.Unlock();
return true;
}
private:
struct Node {
int Value;
Node* Next;
Node(int value, Node* next) : Value(value), Next(next) {
}
};
Node* _head;
Spinlock _lock;
};
Я понимаю, что я должен поставить барьеры памяти. Я могу использовать атомарные переменные:
struct Node {
int Value;
std::atomic<Node*> Next;
Node(int value) : Value(value) {
}
};
std::atomic<Node*> _head;
Spinlock _lock;
...
void Push(int value) {
_lock.Lock();
Node* currentHead = _head.load(std::memory_order_acquire);
Node* newHead = new Node(value);
newHead->Next.store(currentHead, std::memory_order_relaxed);
_head.store(newHead, std::memory_order_release);
_lock.Unlock();
}
bool TryPop(int& value) {
_lock.Lock();
Node* currentHead = _head.load(std::memory_order_acquire);
if (currentHead == nullptr) {
value = NULL;
_lock.Unlock();
return false;
}
value = currentHead->Value;
_head.store(currentHead->Next.load(std::memory_order_relaxed), std::memory_order_release);
delete currentHead;
_lock.Unlock();
return true;
}
Я также могу использовать atomic_thread_fence ():
struct Node {
int Value;
Node* Next;
Node(int value) : Value(value) {
}
};
Node* _head;
Spinlock _lock;
...
void Push(int value) {
_lock.Lock();
Node* currentHead = _head;
std::atomic_thread_fence(std::memory_order_acquire);
Node* newHead = new Node(value);
newHead->Next = currentHead;
std::atomic_thread_fence(std::memory_order_release);
_head = newHead;
_lock.Unlock();
}
bool TryPop(int& value) {
_lock.Lock();
std::atomic_thread_fence(std::memory_order_acquire);
Node* currentHead = _head;
if (currentHead == nullptr) {
value = NULL;
_lock.Unlock();
return false;
}
value = currentHead->Value;
std::atomic_thread_fence(std::memory_order_acquire);
Node* nextNead = currentHead->Next;
std::atomic_thread_fence(std::memory_order_release);
_head = nextNead;
delete currentHead;
_lock.Unlock();
return true;
}
Мои вопросы:
Приобретение блокировки уже устанавливает в памяти гарантии, которые вам нужны.
Когда поток снимает блокировку, он должен записать в атомарный флаг. Это гарантирует, что когда следующий поток получит блокировку и увидит запись в флаг, то поток получения гарантированно увидит все записи, которые сделал освобождающий поток перед записью во флаг.
На sidenote вы должны использовать что-то вроде RAII, чтобы убедиться, что ваша блокировка снята при любых обстоятельствах.
Также вы должны инициализировать вашу блокировку с помощью ATOMIC_FLAG_INIT, иначе она находится в неопределенном состоянии.