首页 > 解决方案 > cpp:如何使类中的向量访问线程安全?

问题描述

我昨天在类似的方向上发布了一些东西,但这个问题专门关于互斥锁,我在引用的“重复”线程中没有找到太多答案。我现在想尝试更一般地问,我希望没关系。

看看这段代码:

#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 先到) 作为输出。即使我尝试尽可能多地使用传递引用、直接指针等来避免复制(原始项目使用相当大的数据量),代码的行为也已定义(总是 t2,然后是 t1 访问)。为什么?我不是在两个线程函数中通过引用直接访问浮点数吗?

你如何使这个向量访问定义明确?我在另一个线程中找到的唯一可能的解决方案是:

- 为互斥锁定义一个类似大小的 unique_ptr 数组(感觉很糟糕,因为我需要能够将数据容器添加到数据存储区,所以这意味着每次更改数据存储区的大小时都要清除数组并重建它?),或者

- 使对向量的访问成为原子的(作为一种想法,这使我的操作成为我想要的线程安全的,但是向量没有原子不变量,或者在某些非 STL-lib 中存在?),或者

- 为数据类中的互斥体编写一个包装器?

对于我的项目而言,哪个线程首先访问并不重要,重要的是我可以通过一个线程将整个向量明确读/写到数据元组中,而无需另一个线程同时操作数据集。

标签: c++multithreadingthread-safetyatomic

解决方案


我相信我现在参考 Sam 的评论做了这件事,而且它似乎有效,这是正确的吗?

#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,我应该在移动实例时不会留下内存泄漏,对吧?


推荐阅读