многопоточность — блокировка стека и блокировки памяти (C ++)

У меня есть реализация блокировки блокировки:

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;
}

Мои вопросы:

  1. Я устанавливаю барьеры памяти?
  2. Что лучше использовать в этом случае (атомарные переменные или atomic_thread_fence) и почему?

4

Решение

Приобретение блокировки уже устанавливает в памяти гарантии, которые вам нужны.

Когда поток снимает блокировку, он должен записать в атомарный флаг. Это гарантирует, что когда следующий поток получит блокировку и увидит запись в флаг, то поток получения гарантированно увидит все записи, которые сделал освобождающий поток перед записью во флаг.

На sidenote вы должны использовать что-то вроде RAII, чтобы убедиться, что ваша блокировка снята при любых обстоятельствах.

Также вы должны инициализировать вашу блокировку с помощью ATOMIC_FLAG_INIT, иначе она находится в неопределенном состоянии.

1

Другие решения


По вопросам рекламы [email protected]