c++multithreadingvalgrinddata-race

Data races resolution C++


I wanted to ask for some help in solving the data races in my program. I started with a simulation of how things should work if I were using multithreading and then modified the code so that I can check if I really obtain those results but I don't know how to resolve the data races in the code. Can someone help me please? My code is the following:

void pi(size_t& id, size_t nThread, size_t N, double& pigreco)
{
    size_t begin = id* N / nThread;
    size_t end = (id+ 1) * N / nThread;

    random_device rd;       // Object to create random seed
    mt19937 generator(rd());    // Mersenne Twister seeded with rd()
    uniform_real_distribution<double> distribution(-1.0, 1.0);

    for (size_t i = begin; i < end; i++) {
        double x = distribution(generator);
        double y = distribution(generator);
        if (sqrt(x*x + y*y) < 1.0)
            pigreco += 4.0 / double(N);
    }
}

int main(int argc, char* argv[])
{
    if (argc != 2) {
        cerr << "Usage: ./pi <number of threads>" << endl;
        exit(EXIT_FAILURE);
    }

    size_t nThread = (size_t)atoi(argv[1]);

    size_t N = 100000000;
    cout << "Generating " << N << " random values using " << nThread << " thread(s)." << endl;

    atomic<double> pigreco = 0.0;

    // create threads
    vector<thread> threads(nThread);
    for (size_t i = 0; i < nThread; i++)
        threads[i] = thread(pi, ref(i), nThread, N, ref(pigreco));

    for_each(threads.begin(), threads.end(), mem_fn(&thread::join));

    cout << "Estimated value for pi: " << pigreco << endl;

    exit(EXIT_SUCCESS);
}

I tried using valgrind in order to find the possible data races and I found out that there is one at the first for loop of the main, I think probably because I use 'i' as argument of my function but that I don't know how to resolve it.


Solution

  • I don't know how to resolve the data races in the code.

    Then stop using std::thread, and especially stop passing references to functions on those threads.

    Here is one way you could avoid passing any references between threads:

    #include <cstdlib>
    #include <iostream>
    #include <random>
    #include <future>
    #include <algorithm>
    
    int pimontecarlo(size_t iterations)
    {
        int count = 0;
    
        std::random_device rd;
        std::mt19937 generator(rd());
        std::uniform_real_distribution<double> distribution(-1.0, 1.0);
    
        for (size_t i = 0; i < iterations; i++) {
            double x = distribution(generator);
            double y = distribution(generator);
            if (sqrt(x*x + y*y) < 1.0) ++count;
        }
    
        return count;
    }
    
    int main(int argc, char* argv[])
    {
        if (argc != 2) {
            std::cerr << "Usage: ./pimontecarlo <number of threads>" << std::endl;
            exit(EXIT_FAILURE);
        }
    
        size_t numThreads { atoi(argv[1]) };
    
        size_t N = 100000000;
        std::cout << "Generating " << N << " random values using " << numThreads << " thread(s)." << std::endl;
    
        // create futures
        std::vector<std::future<int>> results(numThreads);
        for (auto & result : results)
            result = std::async(pimontecarlo, N / numThreads);
    
        int count = 0;
        for (auto & result : results)
            count += result.get();
    
        double pi = (4.0 * count) / N;
    
        std::cout.precision(12);
        std::cout << "Estimated value for pi: " << pi << std::endl;
    
        exit(EXIT_SUCCESS);
    }
    

    See it on coliru