Multithreaded Matrix Multiplication in C++, improving efficiency? Here is a very simple Matrix Multiplication console application using multiple threads (one per row), I'm just looking for feedback to keep improving :) thank you in advance
    #include &lt;iostream&gt;
    #include &lt;fstream&gt;
    #include &lt;sstream&gt;
    #include &lt;vector&gt;
    #include &lt;string&gt;
    #include &lt;thread&gt;

using namespace std;

struct Matrix {
    unsigned int cols = 0;
    unsigned rows = 0;
    int **data;

    Matrix() {};
    Matrix(int r, int c) {
        cols = c;
        rows = r;
        data = new int* [rows];
        for (int i = 0; i &lt; rows; ++i) {
            data[i] = new int[cols];
            for (int j = 0; j &lt; cols; ++j) 
                data[i][j] = 0;            
        }
    }
};

Matrix ReadMatrix(string fileName) {    
    fstream file(fileName, fstream::in);
    Matrix matrix;
    try {
        if (file.is_open()) {            
            int rows, cols;
            file &gt;&gt; rows &gt;&gt; cols;
            matrix = Matrix(rows, cols);

            for (int r = 0; r &lt; matrix.rows; ++r) 
                for (int c = 0; c &lt; matrix.cols; ++c) 
                    file &gt;&gt; matrix.data[r][c];                
            
            file.close();
            cout &lt;&lt; &quot;File: &quot; &lt;&lt; fileName &lt;&lt; &quot; read.\n&quot;;
        }
        else cout &lt;&lt; &quot;could not open file/matrix: &quot; &lt;&lt; fileName &lt;&lt; &quot;\n&quot;;             
        return matrix;
    }
    catch (exception ex) {
        if (file.is_open())file.close();
        throw ex;
    }
}

void MultiplyRow(Matrix m1, Matrix m2, Matrix result, int row) {    
    for (int c = 0; c &lt; m2.cols; ++c)
        for (int i = 0; i &lt; m1.cols; ++i) 
            result.data[row][c] += (m1.data[row][i] * m2.data[i][c]);       
    string str = (&quot;Row: &quot; + to_string(row) + &quot; complete.\n&quot;);
    cout &lt;&lt; str;
}


Matrix MultiplyMatrices(Matrix m1, Matrix m2) {
    try {
        Matrix matrix;
        if (m1.cols == m2.rows) {        

            vector&lt;thread&gt; threads;            
            matrix = Matrix(m1.rows, m2.cols);            
            
            for (int i = 0; i &lt; m1.rows; ++i)
                threads.emplace_back(MultiplyRow, m1, m2, matrix, i);

            for (int i = 0; i &lt; threads.size(); ++i)
                threads[i].join();

            cout &lt;&lt; &quot;Matrices multiplied.\n&quot;;
        }
        else cout &lt;&lt; &quot;cannot multiply matrices\n&quot;;
        return matrix;
    }
    catch (exception ex) {
        throw ex;
    }
}

void WriteMatrixToFile(int **data, int rows, int cols, string fileName) {
    try {
        ofstream file(fileName);
        char SPACE = ' ';
        if (file.is_open()) {
            file &lt;&lt; rows &lt;&lt; SPACE &lt;&lt; cols &lt;&lt; &quot;\n&quot;;
            for (int r = 0; r &lt; rows; ++r) {
                for (int c = 0; c &lt; cols; ++c) {
                    file &lt;&lt; data[r][c] &lt;&lt; SPACE;
                }
                file &lt;&lt; &quot;\n&quot;;
            }
        }
        cout &lt;&lt; &quot;Matrix written to: &quot; &lt;&lt; fileName &lt;&lt; &quot;.\n&quot;;
        file.close();
    }
    catch (exception ex) {
        throw ex;
    }
}

int main()
{
    //read matrices
    Matrix matrix1 = ReadMatrix(&quot;matrix1.txt&quot;); 
    Matrix matrix2 = ReadMatrix(&quot;matrix2.txt&quot;);

    //multiply matrices
    Matrix result = MultiplyMatrices(matrix1, matrix2);

    //put result into results file
    WriteMatrixToFile(result.data, result.rows, result.cols, &quot;result.txt&quot;);
}
Welcome to Code Review!
Algorithm
Currently, a thread is started for every row of the result matrix.  This works best if the matrix has a large number of columns and the number of rows is close to the number of native threads.
A more efficient strategy in general might be to partition the rows to several chunks according to the native thread count instead.  Without careful benchmarking, it's hard to say for sure.
Encapsulation
Matrix has a class invariant — for a non-empty matrix, data points to an array of rows pointers that point to arrays of cols elements each.  To ensure synchronization, the data members can be kept private.
Memory issues
Matrix fails to observe the rule of zero:

Classes that have custom destructors, copy/move constructors or
copy/move assignment operators should deal exclusively with ownership
(which follows from the Single Responsibility Principle). Other
classes should not have custom destructors, copy/move constructors or
copy/move assignment operators.
This rule also appears in the C++ Core Guidelines as C.20: If you can
avoid defining default operations, do.

To fix this issue, simply change the pointers to std::vector.
Storage of matrices
Currently, you are storing a pointer to a dynamic array of pointers, which, in turn, point to dynamic arrays, incurring double indirection overhead and causing memory segmentation.  A more efficient method is to store all elements in a flattened array and provide a helper function to access the element at a given place:
class Matrix {
    std::vector&lt;int&gt; elements;
    std::size_t n_rows;
    std::size_t n_cols;
public:
    Matrix()
        : Matrix(0, 0)
    {
    }
    Matrix(std::size_t n_rows, std::size_t n_cols)
        : elements(n_rows * n_cols), n_rows(n_rows), n_cols(n_cols)
    {
    }
    
    std::size_t row_count() const noexcept {
        return n_rows;
    }
    std::size_t col_count() const noexcept {
        return n_cols;
    }
    
    int&amp; operator()(std::size_t i, std::size_t j) {
        return elements[i * n_cols + j];
    }
    const int&amp; operator()(std::size_t i, std::size_t j) const {
        return elements[i * n_cols + j];
    }
};

Note that this does not preserve the aliasing semantics of your Matrix and performs deep copying instead, so the by-value parameters should be changed to references.
Miscellaneous
using namespace std; is considered bad practice in C++.
If you turn on high warning levels, you will see warnings about mixing signed and unsigned integers in comparisons, which is because you are mixing int and unsigned int.  Instead of them, use std::size_t to store sizes, which is what the language functionalities and the standard library use.
In ReadMatrix, file streams close automatically when they go out of scope, so there's no need to do that manually.  std::ifstream can be used to simplify the construction of the file stream.
Instead of printing messages, throw exceptions when errors occur.