views:

489

answers:

4

I'm trying to share a structure between two threads that is not a global variable. The variable itself is instantiated on the stack in the main function, then its pointer is passed as the parameter to both threads on the start up of both threads.

What I'm finding is that when I change the value of a member of that structure that change is not reflected ever in the other pthread. Is there a way to share a non-global variable (say an integer for example) between two threads, such that changes done to that variable in one thread appear in the other?

This is all because I want to avoid adding global variables for code-maintainability.

A: 

You are doing the right thing, but your experience suggests you are doing it wrong. Please try moving it into a global variable and see if you continue to experience non sharing.

Once you have checked that please post some simple example code illustrating your problem that others can test.

Alex Brown
+2  A: 

Remember that threads get their own stack -- so if you pass an integer in and the function goes out of scope, you're referring to memory that now holds something entirely different.

void foo(void)
{
    pthread_t t1, t2;
    struct foo common_value;

    if (pthread_create(&t1, NULL, a, &common_value) != 0)
    {
        return -1;
    }    

    if (pthread_create(&t2, NULL, b, &common_value) != 0)
    {
        return -1;
    }    

    // upon exiting this function, common_value is no longer what you think it is!
}
brool
Ah! You are fast :) +1
Aviator
Since the non-global is defined in main() according to the question, the main() probably hasn't exited leaving the threads with access to a 'no longer defined' variable.
Jonathan Leffler
+1  A: 

I make no pretence at elegance, but this seems to work for me on MacOS X 10.5.8.

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

static void *writer(void *arg)
{
    int * volatile i = arg;

    for (*i = 1; *i < 10; (*i)++)
    {
        printf("writer(): pseudo_global = %d\n", *i);
        fflush(stdout);
        sleep(1);
    }
    printf("writer(): pseudo_global = %d (exiting)\n", *i);
    fflush(stdout);
    return(0);
}

static void *reader(void *arg)
{
    int * volatile i = arg;
    while (*i < 10)
    {
        printf("reader(): pseudo_global = %d\n", *i);
        fflush(stdout);
        sleep(1);
    }
    printf("reader(): pseudo_global = %d (exiting)\n", *i);
    fflush(stdout);
    exit(0);
}

int main(void)
{
    volatile int pseudo_global = 0;
    pthread_t t1;
    pthread_t t2;
    if (pthread_create(&t1, 0, writer, &pseudo_global) != 0)
    {
        perror("pthread_create() for thread 1");
        exit(1);
    }
    if (pthread_create(&t2, 0, reader, &pseudo_global) != 0)
    {
        perror("pthread_create() for thread 1");
        exit(1);
    }
    while (pseudo_global < 10)
    {
        printf("main():   pseudo_global = %d\n", pseudo_global);
        fflush(stdout);
        sleep(1);
    }
    printf("main():   pseudo_global = %d (exiting)\n", pseudo_global);
    fflush(stdout);
    return(0);
}

Note that I put the 'volatile' qualifier into the code to 'make sure', but apart from eliciting warnings about discarded qualifiers in the calls to pthread_create(), it didn't make any significant difference. I also ran the code without any volatile qualifiers without problem.

This code demonstrates, I think, that on at least one POSIX threads implementation, you can indeed share a local variable on the stack of a function that has not exited while the threads are running.

I am willing to believe I should be more careful about the thread termination and should make sure that main() does not exit without using pthread_join() to ensure that the threads have exited first.

Example output:

$ make ptex
gcc -O ptex.c -o ptex  
ptex.c: In function ‘main’:
ptex.c:40: warning: passing argument 4 of ‘pthread_create’ discards qualifiers from pointer target type
ptex.c:45: warning: passing argument 4 of ‘pthread_create’ discards qualifiers from pointer target type
$ ./ptex
writer(): pseudo_global = 1
main():   pseudo_global = 0
reader(): pseudo_global = 1
writer(): pseudo_global = 2
main():   pseudo_global = 2
reader(): pseudo_global = 2
writer(): pseudo_global = 3
main():   pseudo_global = 3
reader(): pseudo_global = 3
writer(): pseudo_global = 4
main():   pseudo_global = 4
reader(): pseudo_global = 4
writer(): pseudo_global = 5
reader(): pseudo_global = 5
main():   pseudo_global = 5
writer(): pseudo_global = 6
reader(): pseudo_global = 6
main():   pseudo_global = 6
writer(): pseudo_global = 7
reader(): pseudo_global = 7
main():   pseudo_global = 7
writer(): pseudo_global = 8
reader(): pseudo_global = 8
main():   pseudo_global = 8
writer(): pseudo_global = 9
reader(): pseudo_global = 9
main():   pseudo_global = 9
writer(): pseudo_global = 10 (exiting)
reader(): pseudo_global = 10 (exiting)
main():   pseudo_global = 10 (exiting)
$
Jonathan Leffler
I think if not declared as volatile, it will not work. Right?
Aviator
@Aviator: I put the volatile in as a precaution. When removed, I got slightly different sequencing and timing in the output, but the net result was the same.
Jonathan Leffler
@Aviator: Of course, removing the volatile got rid of the warning about discarded qualifiers.
Jonathan Leffler
@Jonathan: Okie.. I got it now. Thanks!
Aviator
I think volatile just means that the compiler will not optimize any code that affects that particular variable right?
ldog
@gmatt: basically, yes; it must do every read of '*i' (aka pseudo_global) as the abstract machine requires - and it may not elide (remove, optimize away) any evaluations of it because it may have changed in ways that the compiler cannot know about.
Jonathan Leffler
A: 

In my opinion sharing a stack variable is worse than having a global variable. At the very least I would allocate some memory on the heap to share between the threads. If the memory is meant to live for the lifetime of the program, you don't even have to worry about freeing it and the main thread can return immediately. With the stack variable, you have to make sure that you don't prematurely pop the stack frame where the variable is located.

Having said that, I think using a global is actually the better solution. Put the global in a separate file and declare it static so that it is only global to the file. Then, only put your thread functions in that file so only they have access to the variable. I like this better since it makes it explicit that the memory is being shared by the threads. The argument to pthread_create is really meant to be thread-specific context, and it won't necessarily be clear from looking at the thread functions that all the threads are being passed the same pointer.

Allan