Есть ли минусы, которые могут заставить не использовать подобные обертки.
Также подскажите, есть ли уже что-то подобное в stl или boost.
Вот накидал пример:
SafeRef:
#include <vector>
#include <iostream>
#include <mutex>
template<typename RefType>
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<const std::vector<int>&> getVector() const
{
return SafeRef<const std::vector<int>&>(m_mutex, m_vector);
}
void reserve(int n)
{
std::lock_guard<std::mutex>(this->m_mutex);
m_vector.reserve(n);
}
void addElement(int i)
{
std::lock_guard<std::mutex>(this->m_mutex);
m_vector.push_back(i);
}
private:
mutable std::mutex m_mutex;
std::vector<int> 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<int> 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
.
Кроме того, обычно вам нужно сделать потокобезопасным не один объект, а группу объектов и их отношения между собой. Например, у вас есть очередь и стек, как в алгоритме сортировочной станции; нет смысла иметь отдельно защищённый стек и отдельно очередь, потому что при этом они могут рассогласоваться во время вашей операции! Например, если вы переносите все элементы из стека в очередь, то после удаления всех элементов из стека перед добавлением в очередь в стеке могут образоваться новые элементы, и инвариант вашего алгоритма нарушится.
Вам придётся заводить явный мьютекс, и защищать не объекты, а ваши операции с объектами.
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Uncaught TypeError: $ajax is not a function at HTMLParagraphElement
На рабочем столе есть бэкап рабочей базы данныхНа сервере есть старая версия БД без тех записей в таблицах, что есть в первой
Допустим, есть метод, который должен возвращать экземпляр анонимного классаДелаю всё, как в этой статье, но при запуске выкидывает следующее: