Страницы

Поиск по вопросам

пятница, 21 декабря 2018 г.

Потокобезопасная обертка над объектом

Есть ли минусы, которые могут заставить не использовать подобные обертки. Также подскажите, есть ли уже что-то подобное в stl или boost. Вот накидал пример:
SafeRef:
#include #include #include
template class SafeRef { public:
SafeRef(std::mutex &mutex, RefType ref) : m_reference(ref), m_mutex(mutex) { m_mutex.lock(); } SafeRef(SafeRef &&other) : m_mutex(other.m_mutex), m_reference(other.m_referance) { }
RefType get() const { return m_reference; }
~SafeRef() { m_mutex.unlock(); } private: std::mutex &m_mutex; RefType m_reference; };
Тестовый код:
class SomeObj { public: SafeRef&> getVector() const { return SafeRef&>(m_mutex, m_vector); } void reserve(int n) { std::lock_guard(this->m_mutex); m_vector.reserve(n); } void addElement(int i) { std::lock_guard(this->m_mutex); m_vector.push_back(i); } private: mutable std::mutex m_mutex; std::vector m_vector; //Большой вектор, копирование очень затратно };
void write(SomeObj &obj) { obj.reserve(500); for (int i = 0; i < 500; i++) { obj.addElement(i); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } }
void read(const SomeObj &obj) { for (int i = 0; i < 5; i++) { std::this_thread::sleep_for(std::chrono::seconds(1)); auto v = obj.getVector(); std::cout << v.get().size() << std::endl; } }
int main() { SomeObj obj; std::thread thread1(&write, std::ref(obj)); std::thread thread2(&read, std::ref(obj)); thread2.join(); thread1.join();
return 0; }


Ответ

Проблема этого подхода в том, что потокобезопасность намного сложнее
Вы не можете просто так сделать объект потокобезопасным, защитив все операции мьютексом. К примеру, у вас есть обёртка над стеком, дававйте посмотрим тогда на код
SupposedlySafeStack s; // ... if (!s.is_empty()) s.pop();
Проблема здесь в том, что между выполнением if (!s.is_empty()) и s.pop(); стек вполне мог стать пустым.
Для хорошего потокобезопасного стека нужен другой набор операций. Например, атомарный bool try_pop(T&) и push_range(Iterator begin, Iterator end). Потокобезопасность нужно планировать на уровне внешнего интерфейса класса.
То же относится к доступу по индексу к std::vector и так далее. Поэтому обёртки, которые оборачивают все публичные функции, непригодны: они лишь создают ложное чувство безопасности!
А обёртка на целый объект уже существует, это std::lock_guard

Кроме того, обычно вам нужно сделать потокобезопасным не один объект, а группу объектов и их отношения между собой. Например, у вас есть очередь и стек, как в алгоритме сортировочной станции; нет смысла иметь отдельно защищённый стек и отдельно очередь, потому что при этом они могут рассогласоваться во время вашей операции! Например, если вы переносите все элементы из стека в очередь, то после удаления всех элементов из стека перед добавлением в очередь в стеке могут образоваться новые элементы, и инвариант вашего алгоритма нарушится.
Вам придётся заводить явный мьютекс, и защищать не объекты, а ваши операции с объектами

Комментариев нет:

Отправить комментарий