2

Does accessing the same array's different elements create a data race?

I have a "Matrix" wrapper class for an array with matrix interface, and i wrote a parallel multiplication by a scalar function for it.

I use CTPL library for thread pools.

I know that writing from a thread into an array cell passed by reference is not a data race (please correct me if i'm wrong) so i decided to pass a cell from the array to the function so i can write multiplication result into the cell itself, not by passing the reference to an array and the index, so i can avoid a data race.

I ran the function 10k times and the results did not differ even once, but a sanitizer i use ("-fsanitize=thread -fPIE -pie -g" in Cmake flags) still alerts me of a data race on the line where i create the thread pool.

Is the sanitizer mistaken or am i really experiencing a data race somewhere?

Here are the pieces of code, relevant to the prolem:

Wrapper:

class Matrix {
protected:
    int width;
    int height;
    double* matrix;

public:

    Matrix(int m, int n);

    Matrix(int m, int n, const std::vector<double>& values);

    int get_width() {
        return width;
    }

    int get_height() {
        return height;
    }

    double get_element(int row_num, int col_num);

    void set_element(int row_num, int col_num, double el);

    double* get_cell_ref(int row_num, int col_num);
};


Method implementations:

Matrix::Matrix(int m, int n) {
    assert(m > 0 && n > 0);
    matrix = new double[m * n]{0};
    width = n;
    height = m;
}

Matrix::Matrix(int m, int n, const std::vector<double>& values) {
    assert(m > 0 && n > 0 && values.size() == m * n);
    matrix = new double[m * n];
    width = n;
    height = m;
    for (int i = 0; i < m * n; ++i) {
        matrix[i] = values[i];
    }
}

double Matrix::get_element(int row_num, int col_num) {
    assert(check_valid(row_num, col_num, get_width(), get_height()));
    return matrix[col_num + get_width() * row_num];
}

void Matrix::set_element(int row_num, int col_num, double el) {
    assert(check_valid(row_num, col_num, get_width(), get_height()));
    matrix[col_num + row_num * get_width()] = el;
}

double* Matrix::get_cell_ref(int row_num, int col_num) {
    int idx = col_num + get_width() * row_num;

    return &matrix[idx];
}

The function that supposedly has a data race:

Matrix* scalar_multiply_parallel(Matrix* a, double mul, int threadN) {
    auto* b = new Matrix(a->get_height(), a->get_width());

    ctpl::thread_pool thr_pool(threadN);
    std::vector<std::future<void>> futures(a->get_height() * a->get_width());

    for (int i =0; i < a->get_height(); i++) {


            for (int j =0; j < a->get_width(); j++) {
                int idx = j + a->get_width() * i;

                auto util = [&a, &b, i, j, mul](int) {
                    //b->set_element(i, j, a->get_element(i, j) * mul);
                    double *cell;
                    cell = b->get_cell_ref(i, j);
                    *cell = a->get_element(i, j) * mul;
                };

                futures[idx] = thr_pool.push(util);
            }

    }

    for (auto& f: futures) {
        f.get();
    }

    return b;
}

0 Answers0