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

268
28 августа 2017, 08:21

Есть ли минусы, которые могут заставить не использовать подобные обертки.
Также подскажите, есть ли уже что-то подобное в 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;
}
Answer 1

Проблема этого подхода в том, что потокобезопасность намного сложнее.

Вы не можете просто так сделать объект потокобезопасным, защитив все операции мьютексом. К примеру, у вас есть обёртка над стеком, дававйте посмотрим тогда на код

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.

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

Вам придётся заводить явный мьютекс, и защищать не объекты, а ваши операции с объектами.

READ ALSO
Uncaught TypeError: $.ajax is not a function

Uncaught TypeError: $.ajax is not a function

Uncaught TypeError: $ajax is not a function at HTMLParagraphElement

380
Как извлечь из базы mysql отдельный таблицы?

Как извлечь из базы mysql отдельный таблицы?

На рабочем столе есть бэкап рабочей базы данныхНа сервере есть старая версия БД без тех записей в таблицах, что есть в первой

238
Вернуть экземпляр анонимного класса

Вернуть экземпляр анонимного класса

Допустим, есть метод, который должен возвращать экземпляр анонимного классаДелаю всё, как в этой статье, но при запуске выкидывает следующее:

292