简体   繁体   English

cpp:如何使 class 中的向量访问线程安全?

[英]cpp: how to make access of vector in class thread-safe?

I posted something in a similar direction yesterday, but that question was specifically about mutexes and I did not find much answer in the referenced "duplicate" thread.我昨天在类似的方向上发布了一些东西,但这个问题专门关于互斥锁,我在引用的“重复”线程中没有找到太多答案。 I want to try to ask more generally now, I hope thats okay.我现在想尝试更一般地问,我希望没关系。

Look at this code:看看这段代码:

#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();
};

In my expectancy, I would have guessed that I will get a wild mixture of output values depending on which thread first got to the vector value, so in the third "demo" vector with 50x0.3f, I would have expected a mixture of 0.3 (t2 got there first) and 1.3 (t1 got it first) as output.在我的预期中,我猜到我会得到 output 值的狂野混合物,具体取决于哪个线程首先到达向量值,所以在第三个 50x0.3f 的“演示”向量中,我会期望混合 0.3 (t2 先到)和 1.3(t1 先到)为 output。 Even as I tried to use as much pass-by-reference, direct pointers, etc as possible to avoid copying (the original project uses quite large data amounts), the code behaves defined (always t2, then t1 access).即使我尝试尽可能多地使用传递引用、直接指针等来避免复制(原始项目使用相当大的数据量),代码的行为也已定义(总是 t2,然后是 t1 访问)。 Why?为什么? Don't I access the floats directly by reference in both thread functions?我不是在两个线程函数中通过引用直接访问浮点数吗?

How would you make this vector access well-defined?你如何使这个向量访问定义明确? The only possible solutions I found in the other thread were:我在另一个线程中找到的唯一可能的解决方案是:

-define a similar sized array of unique_ptr to mutexes (feels bad because I need to be able to add data containers to the datastore, so that would mean clearing the array and rebuilding it every time I change size of the datastore?), or - 为互斥体定义一个类似大小的 unique_ptr 数组(感觉很糟糕,因为我需要能够将数据容器添加到数据存储区,所以这意味着每次更改数据存储区的大小时都要清除数组并重建它?),或者

-make the access to the vector atomic (which as a thought makes my operation as I want it threadsafe, but there is no atomic invariant for a vector, or is there in some non-STL-lib?), or - 使对向量的访问成为原子的(作为一种想法,这使我的操作成为我想要的线程安全的,但是向量没有原子不变量,或者在某些非 STL-lib 中存在?),或者

-write a wrapper for a mutex in the data class? - 为数据 class 中的互斥锁写一个包装器?

It's for my project not important which thread accesses first, it's only important that I can definitifly read/write the whole vector into the data tupel by a thread without another thread manipulating the dataset at the same time.对于我的项目而言,哪个线程首先访问并不重要,重要的是我可以通过一个线程将整个向量明确读/写到数据元组中,而无需另一个线程同时操作数据集。

I believe I did this now with reference to Sam's comments, and it seems to work, is this correct?我相信我现在参考 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();
};

By usage of the unique_ptr, I should leave no memory leaks when I move the instance, right?通过使用 unique_ptr,我应该在移动实例时不会留下 memory 泄漏,对吧?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM