Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can threads write to different elements of same array of structures without locking?

I'm trying to use threads (for the first time!) in a GCC C application which works fine in non-threaded mode. When I run it some threads give results which are all zero instead of the required answers (which I know for checking purposes), but the threads giving zeroes are not the same each time I run it. The ones which give non-zero answers are correct, so the code appears to run ok as such. I wonder if anyone can point out areas where I might have something which is not thread-safe.

My own thoughts are it may be due to how I collect results or maybe memory allocation - I use malloc and free but elsewhere in StackOverflow I see that GCC malloc is considered thread-safe if linked with -lpthread (which I am doing). Nothing uses global/static variables - everything is passed as function arguments.

In order to pass results back to main, my threaded routine uses an array of structures. Each thread writes to a distinct element of this array, so they are not trying to write to the same memory. Maybe I need to use some form of locking when writing results even though they don't go to the same element of the structure array?

I followed the recipe for threaded code here: https://computing.llnl.gov/tutorials/pthreads/#Abstract

I attach (simplified) code extracts in case this gives any clues (I may have omitted/modified something incorrectly but I am not asking for anyone to spot bugs, just the general methodology).

typedef struct p_struct { /* used for communicating results back to main */
    int given[CELLS];
    int type;
    int status;
    /*... etc */
} puzstru;

typedef struct params_struct { /* used for calling generate function using threads */
    long seed;
    char *text;
    puzzle *puzzp;
    bool unique;
    int required;
} paramstru;
/* ========================================================================================== */
void *myfunc(void *spv) /* calling routine for use by threads */
{
    paramstru *sp=(paramstru *)spv;
    generate(sp->seed, sp->text, sp->puzzp, sp->unique, sp->required);
    pthread_exit((void*) spv);
}
/* ========================================================================================== */
int generate(long seed, char *text, puzstru *puzzp, bool unique, int required)
{
/* working code , also uses malloc and free,
    puts results in the element of a structure array pointed to by "puzzp", 
    which is different for each thread
    (see calling routine below :        params->puzzp=puz+thr; )
    extract as follows: */
            puzzp->given[ix]=calcgiven[ix];
            puzzp->type=1; 
            puzzp->status=1;
            /* ... etc */
}
/* ========================================================================================== */


int main(int argc, char* argv[])
{
    pthread_t thread[NUM_THREADS];
    pthread_attr_t threadattr;
    int thr,threadretcode;
    void *threadstatus;
    paramstru params[1];

    /* ....... ETC */

/* set up params structure for function calling parameters */
    params->text=mytext;
    params->unique=TRUE;
    params->required=1;

    /* Initialize and set thread detached attribute */
    pthread_attr_init(&threadattr);
    pthread_attr_setdetachstate(&threadattr, PTHREAD_CREATE_JOINABLE);

    for(thr=0; thr<NUM_THREADS; thr++)
    {
        printf("Main: creating thread %d\n", thr);
        params->seed=ran_arr_next(startingseeds); 
        params->puzzp=puz+thr;
        threadretcode = pthread_create(&thread[thr], &threadattr, myfunc, (void *)params); 
        if (threadretcode)
        {
            printf("ERROR; return code from pthread_create() is %d\n", threadretcode);
            exit(-1);
        }
    }

    /* Free thread attribute and wait for the other threads */
    pthread_attr_destroy(&threadattr);
    for(thr=0; thr<NUM_THREADS; thr++)
    {
        threadretcode = pthread_join(thread[thr], &threadstatus);
        if (threadretcode)
        {
            printf("ERROR; return code from pthread_join() is %d\n", threadretcode);
            exit(-1);
        }
        printf("Main: completed join with thread %d having a status of %ld\n",thr,(long)threadstatus);
    }

/* non-threaded code, print results etc ............. */

    free(startingseeds);
    free(puz);
    printf("Main: program completed. Exiting.\n");
    pthread_exit(NULL);
}

For the benefit of others reading this - all the answers were correct, and the answer to the question in the heading is YES, threads can write safely to different elements of the same array of structures, my problem was in the calling routine - the following is the amended code snippet (now works fine):

    paramstru params[NUM_THREADS];

    for(thr=0; thr<NUM_THREADS; thr++)
    {
        printf("Main: creating thread %d\n", thr);
    /* set up params structure for function calling parameters */
        params[thr].text=mytext;
        params[thr].unique=TRUE;
        params[thr].required=1;
        params[thr].seed=ran_arr_next(startingseeds); 
        params[thr].puzzp=puz+thr;
        threadretcode = pthread_create(&thread[thr], &threadattr, myfunc, (void *)&params[thr]); 
        if (threadretcode)
        {
            printf("ERROR; return code from pthread_create() is %d\n", threadretcode);
            exit(-1);
        }
    }
like image 351
RussellG Avatar asked Mar 15 '11 18:03

RussellG


People also ask

Can multiple threads write to the same array?

The answer is no. Each array element has a region of memory reserved for it alone within the region attributed the overall array. Modifications of different elements therefore do not write to any of the same memory locations.

Can two threads acquire the same lock?

Locks only provide mutual exclusion with other threads that acquire the same lock. All accesses to a data variable must be guarded by the same lock.

What will happen if multiple threads accessing the same resource?

Multiple threads accessing shared data simultaneously may lead to a timing dependent error known as data race condition. Data races may be hidden in the code without interfering or harming the program execution until the moment when threads are scheduled in a scenario (the condition) that break the program execution.

What does thread-safe means?

In the simplest of terms threadsafe means that it is safe to be accessed from multiple threads. When you are using multiple threads in a program and they are each attempting to access a common data structure or location in memory several bad things can happen.


2 Answers

To answer your question, it is perfectly fine to write to different elements of the same array from different threads without locking. There will only ever be a data race if two threads write to the same byte without synchronizing (e.g., locking).

As other answers point out, the reason your code as-written breaks is because you pass a pointer to the same params object to each of your threads, an then you modify that object. You probably want to create a new param for each thread.

like image 92
Karmastan Avatar answered Oct 19 '22 23:10

Karmastan


paramstru params[1];

The code is passing the same structure to all threads. Just the thread initialization loop is overwriting the data that a thread should work on:

for(thr=0; thr<NUM_THREADS; thr++)
    {
        printf("Main: creating thread %d\n", thr);
        params->seed=ran_arr_next(startingseeds); /* OVERWRITE  */
        params->puzzp=puz+thr; /* OVERWRITE  */

The reason the non-threaded code works is because each call to myfunc() terminates before the params structure is changed.

like image 28
Apalala Avatar answered Oct 19 '22 22:10

Apalala