cpp: как сделать доступным вектор в классе thread-safe? - PullRequest
0 голосов
/ 09 апреля 2020

Вчера я опубликовал что-то в похожем направлении, но этот вопрос был конкретно о мьютексах, и я не нашел большого ответа в упомянутой «дублирующей» ветке. Я хочу попытаться задать более общий вопрос сейчас, я надеюсь, что все в порядке.

Посмотрите на этот код:

#include <iostream>
#include <mutex>
#include <vector>
#include <initializer_list>
using namespace std;

class Data {
public:

    void write_data(vector<float>& data) {
        datav = move(data);
    }

    vector<float>* read_data() {
        return(&datav);
    }

    Data(vector<float> in) : datav{ in } {};

private:
    vector<float> datav{};
};

void f1(vector<Data>& in) {
    for (Data& tupel : in) {
        vector<float>& in{ *(tupel.read_data()) };
        for (float& f : in) {
            f += (float)1.0;
        };
    };
}

void f2(vector<Data>& in) {
    for (Data& tupel : in) {
        vector<float>& in{ *(tupel.read_data()) };
        for (float& f : in) {
            cout << f << ",";
        };
    };
}
int main() {
    vector<Data> datastore{};
    datastore.emplace_back(initializer_list<float>{ 0.2, 0.4 });
    datastore.emplace_back(initializer_list<float>{ 0.6, 0.8 });
    vector<float> bigfv(50, 0.3);
    Data demo{ bigfv };
    datastore.push_back(demo);
    thread t1(f1, ref(datastore));
    thread t2(f2, ref(datastore));
    t1.join();
    t2.join();
};

В ожидании я бы предположил, что получу дикую смесь выходных значений в зависимости от того, какой поток первым получил значение вектора, поэтому в третьем векторе «демо» с 50x0,3f я бы ожидал смесь 0,3 (t2 добрался там первым) и 1,3 (t1 получил его первым) как вывод. Несмотря на то, что я пытался использовать как можно больше прямых ссылок, прямых указателей и т. Д. 1017 *, чтобы избежать копирования (исходный проект использует довольно большие объемы данных), код ведет себя определенным образом (всегда t2, затем t1-доступ). Почему? Разве я не обращаюсь к плавающим объектам напрямую по ссылке в обеих функциях потока?

Как бы вы сделали этот векторный доступ четким? Единственными возможными решениями, которые я нашел в другом потоке, были:

-определение массива unique_ptr схожего размера с мьютексами (чувствует себя плохо, потому что мне нужно иметь возможность добавлять контейнеры данных в хранилище данных, так что это будет означать очистку массив и перестроение его каждый раз, когда я изменяю размер хранилища данных?), или

- делаем доступ к вектору atomi c (который, как мысль, делает мою работу так, как я хочу, чтобы она была потокобезопасной, но нет атома c, инвариантного для вектора, или есть в какой-то не-STL-lib?), или

-записать обертку для мьютекса в классе данных?

Это для моего проекта не важно, какой поток обращается первым, важно только то, что я могу однозначно прочитать / записать весь вектор в набор данных потоком, при этом другой поток не манипулирует набором данных одновременно.

1 Ответ

0 голосов
/ 10 апреля 2020

Я полагаю, что я сделал это сейчас со ссылкой на комментарии Сэма, и, похоже, это работает, верно?

#include <iostream>
#include <mutex>
#include <vector>
#include <initializer_list>
using namespace std;

class Data {
public:
    unique_ptr<mutex> lockptr{ new mutex };
    void write_data(vector<float>& data) {
        datav = move(data);
    }

    vector<float>* read_data() {
        return(&datav);
    }

    Data(vector<float> in) : datav{ in } {
    };
    Data(const Data&) = delete;
    Data& operator=(const Data&) = delete;
    Data(Data&& old) {
        datav = move(old.datav);
        unique_ptr<mutex> lockptr{ new mutex };
    }
    Data& operator=(Data&& old) {
        datav = move(old.datav);
        unique_ptr<mutex> lockptr{ new mutex };
    }
private:
    vector<float> datav{};
    //mutex lock{};

};

void f1(vector<Data>& in) {
    for (Data& tupel : in) {
        unique_lock<mutex> lock(*(tupel.lockptr));
        vector<float>& in{ *(tupel.read_data()) };
        for (float& f : in) {
            f += (float)1.0;
        };
    };
}

void f2(vector<Data>& in) {
    for (Data& tupel : in) {
        (*(tupel.lockptr)).try_lock();
        vector<float>& in{ *(tupel.read_data()) };
        for (float& f : in) {
            cout << f << ",";
        };
        (*(tupel.lockptr)).unlock();
    };
}
int main() {
    vector<Data> datastore{};
    datastore.emplace_back(initializer_list<float>{ 0.2, 0.4 });
    datastore.emplace_back(initializer_list<float>{ 0.6, 0.8 });
    vector<float> bigfv(50, 0.3);
    Data demo{ bigfv };
    datastore.push_back(move(demo));
    thread t1(f1, ref(datastore));
    thread t2(f2, ref(datastore));
    t1.join();
    t2.join();
};

При использовании unique_ptr я не должен оставлять утечек памяти при перемещении экземпляр, верно?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...