2016-12-16 2 views
7

boost::shared_mutex или std::shared_mutex (C++ 17) может использоваться для одиночной записи, доступа нескольких считывателей. В качестве учебного упражнения я собрал простую реализацию, которая использует спин-блокировку и имеет другие ограничения (например, политику справедливости), но явно не предназначена для использования в реальных приложениях.Реализация C++ shared_mutex

Идея состоит в том, что мьютекс сохраняет счетчик ссылок, равный нулю, если нить не удерживает блокировку. Если> 0, значение представляет количество читателей, имеющих доступ. Если -1, у одного писателя есть доступ.

Является ли это правильной реализацией (в частности, с использованием минимальных порядков памяти), которая не содержит данных?

#include <atomic> 

class my_shared_mutex { 
    std::atomic<int> refcount{0}; 
public: 

    void lock() // write lock 
    { 
     int val; 
     do { 
      val = 0; // Can only take a write lock when refcount == 0 

     } while (!refcount.compare_exchange_weak(val, -1, std::memory_order_acquire)); 
     // can memory_order_relaxed be used if only a single thread takes write locks ? 
    } 

    void unlock() // write unlock 
    { 
     refcount.store(0, std::memory_order_release); 
    } 

    void lock_shared() // read lock 
    { 
     int val; 
     do { 
      do { 
       val = refcount.load(std::memory_order_relaxed); 

      } while (val == -1); // spinning until the write lock is released 

     } while (!refcount.compare_exchange_weak(val, val+1, std::memory_order_acquire)); 
    } 

    void unlock_shared() // read unlock 
    { 
     refcount.fetch_sub(1, std::memory_order_relaxed); 
    } 
}; 

ответ

3

(я использую CMPXCHG как стенография для функции C++ compare_exchange_weak, не cmpxchg instruction).

lock_shared определенно выглядит неплохо: вращающийся при чтении и только попытка cmpxchg, когда значение намного лучше для производительности, чем вращение на cmpxchg. Хотя я думаю, что вас принудили к правильности, чтобы избежать изменения -1 до 0 и разблокировки блокировки записи.

Я думаю unlock_shared следует использовать mo_release, не mo_relaxed, так как он должен заказать грузы из общей структуры данных, чтобы убедиться, что писатель не начать писать до того, как грузы из критической секции чтения происходит. (LoadStore reordering - вещь на слабоупорядоченных архитектурах, хотя x86 только переупорядочивает StoreLoad.) A Release operation will order preceding loads and keep them inside the critical section.


(в записи lock): // может memory_order_relaxed быть использован, если только один поток берет блокировки записи?

Нет, вам все равно нужно держать пишет в критической секции, поэтому CMPXCHG еще нужно синхронизировать с (в терминологии C++) высвобождают-магазины от unlock_shared.

+0

Я не был уверен в порядках памяти в unlock_shared, но мои рассуждения состояли в том, что он действительно не «выпускает» что-либо, поскольку имеет доступ только для чтения и не может изменить данные, которые он защищает. – LWimsey

+0

@LWimsey: да, это тяжелее думать о заказе груза, чем заказать магазин, но это реальная вещь. Нагрузка становится глобально видимой в тот момент, когда она считывает данные из кеша L1. (Потому что это то, что делает копию из глобально-когерентного кеша в ядро ​​из-за порядка одного процессора.) –