Home > Net >  Unexpected Behavior With Pthread, Barriers, and Sharing Array
Unexpected Behavior With Pthread, Barriers, and Sharing Array

Time:09-17

I have a program which uses MPI Pthread. I'm stuck on implementing pthreads to share an array for read/writes. I made mock code here, which mimics the issue.

#include <iostream>
#include <unistd.h>
#include <pthread.h>

struct args {
    double* array;
    int start;
    int stop;
    double myVal;
    double* row;
    pthread_barrier_t* barrier;
};

void* mythread(void* arguments){
    struct args* args_ = (struct args*)arguments;
    double* array = args_->array;
    int start = args_->start;
    int stop = args_->stop;
    double myVal = args_->myVal;
    pthread_barrier_t* barrier = args_->barrier;
    double* row = args_->row;

    for(int i = start; i < stop; i  ){
        pthread_barrier_wait(barrier);
        for(int j = 0; j < 10; j  ){
            double a = row[j];
            int ind = i*10   j;
            array[ind] = a   myVal;
        }
    }
}


int main(){

    pthread_t threads[50];
    int start_ = 0;
    double* array_0 = NULL;
    array_0 = new double[100*10];
    double* row = NULL;
    row = new double[10];
    pthread_barrier_t barrier;
    (void)pthread_barrier_init(&barrier, NULL, 50 1);

    for(int n = 0; n < 50; n  ){
        struct args args_;
        args_.start = start_;
        args_.stop = start_   2;
        start_ = start_   2;
        args_.array = &array_0[0];
        args_.myVal = n;
        args_.row = row;
        args_.barrier = &barrier;
        (void)pthread_create(&threads[n], NULL, mythread, (void*)&args_);
    }

    for(int i = 0; i < 2; i  ){
        for(int k = 0; k < 10; k  ){
            row[k] = i 1;
        }
        // usleep(100);
        pthread_barrier_wait(&barrier);
    }

    for(int n = 0; n < 50; n  ){
        (void)pthread_join(threads[n], NULL);
    }
    

    // print
    for(int i = 0; i < 100; i  ){
        for(int j = 0; j < 10; j  ){
            int ind = i*10   j;
            std::cout << " " << array_0[ind];
        }
        std::cout << std::endl;
    }
    return 0;
}

Main spawns 50 threads. Barrier is initialized with 50 1 (to include main thread). This should sync all 51 threads on the pthread_barrier_wait() call, but the blocking wait call doesn't seem to allow the "row" array write loop to complete before releasing.

The expected result should be:

1 1 1 1 1 1 1 1 1 1
2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2
3 3 3 3 3 3 3 3 3 3
3 3 3 3 3 3 3 3 3 3
4 4 4 4 4 4 4 4 4 4
4 4 4 4 4 4 4 4 4 4
5 5 5 5 5 5 5 5 5 5
5 5 5 5 5 5 5 5 5 5
.
.
.
.
.
.
etc.

The actual output is semi-random. It completes the sequence in some threads, and in others it shows zeros, as if "row" never got filled in. Adding usleep() after writing to "row" array doesn't help either - not that I can afford to have sleep functions in my code. This leads me to believe I don't understand how pointer arrays are properly shared between threads. I'm new to C so any help appreciated.

CodePudding user response:

In your loop, you create a struct args object, and then you pass the address of this object to pthread_create. The object is then immediately "destroyed" at the end of the loop iteration, and a new one is created at the next iteration, however, the newly created thread still has a reference to this old "destroyed" object.

You need to make sure that the object you pass to pthread_create persists long enough for either:

  1. The thread to make its own copy of it
  2. The thread to finish

As a very simple approach, you can instead move the declaration of _args to outside of the loop and turn it into an array like this:

struct args args_[50];

for(int n = 0; n < 50; n  ){
    args_[n].start = start_;
    args_[n].stop = start_   2;
    start_ = start_   2;
    args_[n].array = &array_0[0];
    args_[n].myVal = n;
    args_[n].row = row;
    args_[n].barrier = &barrier;
    (void)pthread_create(&threads[n], NULL, mythread, (void*)&args_[n]);
}

The lifetime of args_[] is now longer than each of the threads. Alternatively you can dynamically allocate your struct args (e.g. with new) and consume the object in the thread (e.g. with delete). Or if you are using C 11 or later, you can use std::shared_ptr and std::thread, there is some documentation about how these two behave with each other here.

  • Related