从多个线程访问随机数引擎

时间:2014-04-19 16:45:04

标签: c++ multithreading random

这是我的第一个问题,所以请原谅我违反政策的行为。我希望每个线程有一个全局随机数引擎,为此我设计了以下方案:我开始的每个线程从原子全局int获得一个唯一索引。存在随机引擎的静态向量,其第i个成员被认为由具有索引i的线程使用。如果索引大于矢量大小元素以同步方式添加到它。为了防止性能损失,我检查两次索引是否大于向量大小:一次是以未同步的方式,一次是在锁定互斥锁之后。到目前为止一切顺利,但以下示例因各种错误(堆损坏,malloc错误等)而失败。

#include<vector>
#include<thread>
#include<mutex>
#include<atomic>
#include<random>
#include<iostream>

using std::cout;

std::atomic_uint INDEX_GEN{};
std::vector<std::mt19937> RNDS{};
float f = 0.0f;
std::mutex m{};

class TestAThread {
public:
TestAThread() :thread(nullptr){
    cout << "Calling constructor TestAThread\n";
    thread = new std::thread(&TestAThread::run, this);
}

TestAThread(TestAThread&& source) : thread(source.thread){
    source.thread = nullptr;
    cout << "Calling move constructor TestAThread. My ptr is " << thread << ". Source ptr is" << source.thread << "\n";
}

TestAThread(const TestAThread& source) = delete;

~TestAThread() {
    cout << "Calling destructor TestAThread. Pointer is " << thread << "\n";
    if (thread != nullptr){
        cout << "Deleting thread pointer\n";
        thread->join();
        delete thread;
        thread = nullptr;
    }
}

void run(){
    int index = INDEX_GEN.fetch_add(1);
    std::uniform_real_distribution<float> uniformRnd{ 0.0f, 1.0f };

    while (true){
        if (index >= RNDS.size()){
            m.lock();
            // add randoms in a synchronized manner.
            while (index >= RNDS.size()){
                cout << "index is " << index << ", size is " << RNDS.size() << std::endl;
                RNDS.emplace_back();
            }
            m.unlock();
        }

        f += uniformRnd(RNDS[index]);
    }
}

std::thread*    thread;
};

int main(int argc, char* argv[]){
std::vector<TestAThread> threads;
for (int i = 0; i < 10; ++i){
    threads.emplace_back();
}

cout << f;
}

我做错了什么?!

1 个答案:

答案 0 :(得分:2)

显然f += ...无论右手边都是竞赛条件,但我想你已经知道了。

我看到的主要问题是您使用全球std::vector<std::mt19937> RNDS。您的受互斥锁保护的关键部分仅包含添加新元素;不访问现有元素:

... uniformRnd(RNDS[index]);

这不是线程安全的,因为在另一个线程中调整RNDS可能会导致RNDS[index]移动到新的内存位置。事实上,这可能发生在计算引用RNDS[index]uniformRnd开始使用它之前,在这种情况下,uniformRnd认为是Generator&将是悬空指针,可能是新创建的对象。无论如何,uniformRnd的{​​{1}}不保证数据竞赛[注1],operator()的{​​{1}}也不保证。

您可以通过以下方式解决此问题:

  1. 计算受保护部分内生成器的引用(或指针)(不能取决于容器的大小是否足够),

  2. 使用RNDS而不是operator[],它在调整大小时不会使引用无效(除非通过调整大小从容器中删除引用的对象)。

  3. 这样的事情(关注竞争条件;还有其他事情,我可能会采取不同的做法):

    std::deque

    [1] std::vector std::mt19937& get_generator(int index) { std::lock_guard<std::mutex> l(m); if (index <= RNDS.size()) RNDS.resize(index + 1); return RNDS[index]; } void run(){ int index = INDEX_GEN.fetch_add(1); auto& gen = get_generator(index); std::uniform_real_distribution<float> uniformRnd{ 0.0f, 1.0f }; while (true) { /* Do something with uniformRnd(gen); */ } } 的原型是operator()。换句话说,参数必须是 mutable 引用,这意味着它不是隐式线程安全的;只有标准库函数的uniformRnd个参数没有数据竞争。