C++ Pthreads API

Ever created a multi-threaded application? Using POSIX threads, called Pthreads you can create multi-threaded applications in almost any operating system. Pthreads give you the ability to control threads using C++. In this example, we will create a multi-threaded Matrix Multiplication Application.

I will be using pthreads, but you have the option of defining how I get our Matrix input, I used two files.

The following code can only run under Linux, though it will run under Windows with the required library like pthreads-w32.

Note: You may replace any qstring or q_ function with any function of your choice that does the same thing. For qstring, you can use string std class. For qDebug() you can replace it with std cout.

int main(int argc, char** argv){
    int rc, status;
    if(argc > 3){ // Make sure there is 3 arguments.
        Matrix M = readMatrix(argv[1]); // Read the first input file, store it as Matrix M
        Matrix N = readMatrix(argv[2]); // Read the 2nd input file, store it as Matrix N
        QString Results(argv[3]); // Get output file name
        if(M.columns != N.rows){ // Cannot multiply matrices if the first one's columns are not equal to the second one's rows
            qDebug() << "Error: Cannot multiply these two matrices";
            return 0;
        }
        pthread_t threads[(N.columns*M.rows)]; // Create a pthread_t
        pthread_attr_t attr; // Create attribute for pthread
         /* Initialize and set thread detached attribute */
        pthread_attr_init(&attr); // initialize attribute
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); // make the attribute have JOINABLE option
        C = new Matrix; // Resulting C matrix
        C->rows = M.rows; // set result rows equal to the first matrix's row
        C->columns = N.columns; // set result column equal to the second matrix's column
        int t = 0;
        for(int r = 0; r < M.rows; r++){ // Loop rows 1st Matrix
            for(int c = 0; c < (N.columns); c++) { // Loop columns 2nd Matrix
                for(int i = 0; i < M.columns; i++){ // Loop columns 1st Matrix
                    thread_data_array[t].thread_a_row[i] = M.V[r][i]; // Get first matrices row
                }
                for(int i = 0; i < N.rows; i++){ // Loop rows 2nd Matrix
                    thread_data_array[t].thread_b_column[i] = N.V[i][c]; // Get second matrices rows
                }
                thread_data_array[t].thread_id = t; // set thread id
                thread_data_array[t].a = M.columns; // set first matrix's columns as a
                thread_data_array[t].b = N.rows; // set second matrix's rows as b
                rc = pthread_create(&threads[t], &attr, calcMatrix, (void *) &thread_data_array[t]); // Create Thread
                // Set pthread_t, then attribute attr, then calcMatrix our calculating function, give it the argument struct.
                if (rc) { // if there is an error
                    qDebug() << "ERROR: " << rc << endl;
                    exit(-1);
                }
                /* Free attribute and wait for the other threads */
                t++; // increment thread
            }
        }
        pthread_attr_destroy(&attr); // destroy the attribute.
        int thr = 0;
        for(int r = 0; r < M.rows; r++){
            for(int c = 0; c < N.columns; c++){
                rc = pthread_join(threads[thr], (void **)&status); // Wait for the thread to end and join this function
                if (rc) { // check for error
                     qDebug() << "ERROR join";
                     return 0;
                }
                C->V[r][c] = 0;
                for(int i = 0; i < N.rows; i++){ // Get the result of that one matrix multiplication
                    C->V[r][c] = thread_data_array[thr].x;
                }
                thr++;
            }
        }
        // You can replace qDebug() with cout, and qPrintable isn't important.
        qDebug() << "Results recorded to " << Results << ": ";
        qDebug() << qPrintable(PrintMatrix(C)); // Print results on screen
        pthread_exit(NULL); // exit thread
    } else {
        qDebug() << "Please include the first matrix file, the second matrix file, and the output file in order for this program's arguments."; // in case there is an error
    }
   
    return 0;
}

We first check for our arguments in command line, get out 2 Matrix classes.

Matrix class

Our Matrix classes look like this:

class Matrix {
public:
    int V[30][30];
    int rows;
    int columns;
}; // a simple matrix class

Matrix* C = 0;

The V represents our variables in rows and columns as a 2 dimensional array.

thread_data Structure, for Thread Arguments

Our thread_data class looks like this, it contains arguments for our threads, you can edit it as much as you want:

struct thread_data
{
   int  thread_id;
   int thread_a_row[30];
   int thread_b_column[30];
   int x;
   int a;
   int b;
};

struct thread_data thread_data_array[20];

pthread_t structure

pthread_t threads[(N.columns*M.rows)];, this will create a number of threads that we will be using in our calculation function.

pthread_attr_t attribute structure

pthread_attr_t attr; // Create attribute for pthread
         /* Initialize and set thread detached attribute */
        pthread_attr_init(&attr); // initialize attribute
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); // make the attribute have JOINABLE option

We set our attribute for our threads as PTHREAD_CREATE_JOINABLE, which means we can use pthread_join() to combine each thread with the main thread, and wait for it to do this. This allows us to sync our threads back to a single thread.

We create our Resulting Matrix C, which is our answer, and we add our arguments for each calcMatrix thread using thread_data_array.

pthread_create function

rc = pthread_create(&threads[t], &attr, calcMatrix, (void *) &thread_data_array[t]); // Create Thread

We create each thread, add our attribute, add our calcMatrix (which is a function not a variable), and our argument thread_data_array, which has all our variables.

pthread_attr_destroy(&attr); // destroy the attribute.
Free the attribute.

pthread_join thread function

rc = pthread_join(threads[thr], (void **)&status); // Wait for the thread to end and join this function

This will join each thread to the main thread, so that we are synced up.

C->V[r][c] = thread_data_array[thr].x;
Add our results into our resulting matrix class.

pthread_exit(NULL); // exit thread
Exit the thread

CalcMatrix function

Here's our calcMatrix function:

void *calcMatrix(void *threadarg) { // calculate Matrix multithreaded function
   int taskid;
   struct thread_data *my_data;

   sleep(1); // Sleep 1 ms to not overburden the CPU
   my_data = (struct thread_data *) threadarg; // Get the argument structure
   taskid = my_data->thread_id;
   my_data->x = 0;
   for(int i = 0; i < my_data->a; i++) { // loop through until a is finished
     my_data->x += my_data->thread_a_row[i]*my_data->thread_b_column[i]; // multiply and add together
   }
   pthread_exit(NULL); // exit thread
}

calcMatrix is going to be provided to each thread to use as a function. It will grab our thread_data from arguments, multiply the variables. Exit the pthread.

Reminder

Remember to #include <pthread.h>

Post new comment

name
The content of this field is kept private and will not be shown publicly. If you have a Gravatar account, used to display your avatar.