написал сегодня вот такой класс: Код | #include <windows.h>
#include <cassert>
namespace atomic { template<class T> __forceinline T* exchange(T* volatile* p, T* v) { return reinterpret_cast<T*>( InterlockedExchangePointer( reinterpret_cast<PVOID volatile*>(p) , reinterpret_cast<PVOID>(v) )); }
template<class T> __forceinline T* compare_exchange(T* volatile* dest, T* exch, T* cmp) { return reinterpret_cast<T*>( InterlockedCompareExchangePointer( reinterpret_cast<PVOID volatile*>(dest) , reinterpret_cast<PVOID>(exch) , reinterpret_cast<PVOID>(cmp) )); } }
namespace lockfree { // Массив фиксированого размера template<class T, size_t SIZE, class Allocator> class Block { public:
typedef size_t size_type; typedef T value_type; typedef T& reference_type; typedef T const& const_reference_type; typedef T* pointer_type;
Block(Allocator const& a = Allocator()) : alloc_(a) , data_(reinterpret_cast<pointer_type>(storage_)) , end_(reinterpret_cast<pointer_type>(storage_)) , visible_(reinterpret_cast<pointer_type>(storage_)) { }
Block(Block const& other) : alloc_(other.alloc_) , data_(reinterpret_cast<pointer_type>(storage_)) , end_(reinterpret_cast<pointer_type>(storage_)) , visible_(reinterpret_cast<pointer_type>(storage_)) { // копирование данных // до того как конструктор завершит работу, можно считать, // что объект используется только из одного потока for(int i = 0; i < other.size(); ++i) { alloc_.construct(end_, other[i]); end_++; } visible_ = end_; // если конструктор копирования одного из элементов бросит исключение, // деструктор сможет удалить частично инициализированый контейнер }
// оператор присваивания - не потокобезопасен Block& operator = (Block const& other) { // содержимое массива невидимо для других потоков atomic::exchange(&visible_, data_);
// разрушение старых элементов массива for(pointer_type p = data_; p < end_; ++p) alloc_.destroy(p);
end_ = data_;
// копирование новых данных try { for(pointer_type p = other.data_; p < other.end_; ++p) { alloc_.construct(end_, *p); // копирование end_++; } atomic::exchange(&visible_, end_); // изменения теперь видимы для других потоков } catch(...) { // конструктор копирования одного из объектов бросил исключение // старые данные - потеряны, новые - скопированы частично for(pointer_type p = data_; p < end_; ++p) alloc_.destroy(p);
end_ = data_; atomic::exchange(&visible_, data_); throw; } }
~Block() { for(pointer_type p = data_; p < end_; ++p) alloc_.destroy(p); }
// возвращает размер буфера - не потокобезопасен size_type size() const { return visible_ - data_; }
// возвращает true, если массив пуст - не потокобезопасен bool empty() const { return visible_ == data_; }
// возвращает элемент массива - метод не потокобезопасен reference_type operator [] (size_type index) { assert(index < SIZE); return data_[index]; }
// возвращает элемент массива - метод не потокобезопасен const_reference_type operator [] (size_type index) const { return data_[index]; }
// возвращает ссылку на последний элемент массива - метод не потокобезопасен reference_type back() { return *(visible_ - 1); }
// возвращает ссылку на последний элемент массива - метод не потокобезопасен const_reference_type back() const { return *(visible_ - 1); }
// возвращает ссылку на первый элемент массива - метод не потокобезопасен reference_type front() { return *data_; }
// возвращает ссылку на первый элемент массива - метод не потокобезопасен const_reference_type front() const { return *data_; }
// метод добавляет элемент в массив // если добавить нельзя(буфер заполнен) - возвращает false // иначе - true bool push_back(const_reference_type value) { while(true) { pointer_type v = visible_; pointer_type e = end_; pointer_type n = e + 1;
// states: if (n > data_ + SIZE) return false;
// атомарная проверка и обмен pointer_type p = atomic::compare_exchange(&end_, n, v);
if (p == v && p == e) { // место в массиве выделено alloc_.construct(p, value); atomic::exchange(&visible_, n); // данные - видимы для других потоков break; } // live lock } return true; }
// удаляет последний элемент блока // перед тем как удалить - копирует его в value, используя оператор присваивания value_type // если элементов нет - возвращает false // иначе - true bool pop_back(reference_type value) { while(true) { pointer_type v = visible_; pointer_type e = end_; pointer_type n = e - 1;
// states: if (n < data_) return false;
pointer_type p = atomic::compare_exchange(&end_, n, v);
if (p == v && p == e) { value = *n; alloc_.destroy(n); atomic::exchange(&visible_, n); break; } } return true; }
private:
Allocator alloc_; char storage_[SIZE*sizeof(T)]; pointer_type data_; mutable volatile pointer_type end_; mutable volatile pointer_type visible_; };
}
|
это буфер, методы push_back и pop_back которого можно вызывать из разных потоков в принципе, код проходит все тесты, но, есть сомнения главное сомнение - интерфейс, здесь можно наделать ошибок, например один поток вызвал back(), а другой в это время pop_back ну и тд я думал сделать какой-нибудь другой интерфейс, что-бы, если какой-то код работает с контейнером конкурентно, то всякие небезопасные методы нельзя было вызвать может какой-нибудь объект посредник создавать?
|