2015-06-21 3 views
4

У меня есть спин-блокировка реализации:Спин заперта стека и память Барьеры (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) и почему?

ответ

1

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

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

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

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

Смежные вопросы