How memory is shared between threads in C

I am trying to understand how memory is shared between threads.

I know that every thread has his own stack while heap is shared between every threads. Every thread shares common addressing space, so a local variable inside a thread can be seen by another thread using pointers. This is done by using POSIX library pthread, in Linux.

So, assumming that it is right, if I create a thread with a local var allocated in his stack, another thread should read a wrong value if the stack frame containing var is destroyed. With this code, it works in this way.

void *_th2(void *args) {

    sleep(1);
    printf("0x%x\n", *(int *)args);
    fflush(stdout);

    pthread_exit(NULL);
}

void *_th1(void *args) {
    pthread_t tid;
    int var = 10;

    pthread_create(&tid, NULL, _th2, (void *)&var);
    pthread_exit(NULL);
}

But if I create var with a malloc to allocate it in the heap, it doesn't show right value. Why? The code is below

void *_th2(void *args) {

    sleep(1);
    printf("0x%x\n", *(int *)args);
    fflush(stdout);

    pthread_exit(NULL);
}

void *_th1(void *args) {
    pthread_t tid;
    int *var = malloc(sizeof *var);

    *var = 10;
    pthread_create(&tid, NULL, _th2, (void *)var);
    pthread_exit(NULL);
}

Upvotes: 0

Views: 3958

Answers (3)

John Bollinger
John Bollinger

Reputation: 180113

I know that every thread has his own stack while heap is shared between every threads. Every thread shares common addressing space, so a local variable inside a thread can be seen by another thread using pointers. This is done by using POSIX library pthread, in Linux.

Some of these details may vary with the operating system and threading implementation, but POSIX does specify that

Anything whose address may be determined by a thread, including but not limited to static variables, storage obtained via malloc(), directly addressable storage obtained through implementation-defined functions, and automatic variables, are accessible to all threads in the same process.

(emphasis added).

if I create a thread with a local var allocated in his stack, another thread should read a wrong value if the stack frame containing var is destroyed.

No, you have it pretty much backwards. What you can say is that any thread is permitted to read the value of an automatic variable only during that variable's lifetime. The C specifications don't mention stacks at all, but in a stack-based implementation, an automatic variable's lifetime ends when the stack frame to which it belongs is popped, or earlier. After the end of a variable's lifetime, attempting to read its value via a pointer produces undefined behavior. Among the many possible behaviors that could manifest are that any value at all might be read, including the value held by the variable at the end of its lifetime.

But if I create var with a malloc to allocate it in the heap, it doesn't show right value. Why?

You have not presented a complete example, but when I combined the functions you presented with this main():

int main(void) {
    _th1(NULL);
    sleep(3);
    return 0;
}

, the resulting program printed

0xa

which shows that the second thread is indeed correctly reading the value stored in the allocated object, as should be expected, to the extent that it runs before the program terminates.

The sleep() in main() is present to make it probable (but not certain) that the overall program does not terminate before the second thread runs to completion. In practice, one ought to join each thread for certainty, but the original functions do not make that possible, and I elected not to modify them.

Upvotes: 0

Jonathan Leffler
Jonathan Leffler

Reputation: 753585

Here's a not-quite minimal MCVE (Minimal, Complete, Verifiable Example) program closely based on what is shown in the question:

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

static int join = 1;

static void *th2(void *args)
{
    printf("%s: %d (%p)\n", __func__, *(int *)args, args);
    sleep(1);
    printf("0x%X\n", *(int *)args);
    fflush(stdout);
    pthread_exit(NULL);
}

static void *th1(void *args)
{
    assert(args == NULL);
    pthread_t tid;
    int var = 10;

    printf("%s: %d (%p)\n", __func__, var, (void *)&var);
    pthread_create(&tid, NULL, th2, &var);
    if (join)
        pthread_join(tid, NULL);
    pthread_exit(NULL);
}

/*---*/

static void *th4(void *args)
{
    printf("%s: %d (%p)\n", __func__, *(int *)args, args);
    sleep(1);
    printf("0x%X\n", *(int *)args);
    fflush(stdout);
    pthread_exit(NULL);
}

static void *th3(void *args)
{
    assert(args == NULL);
    pthread_t tid;
    int *var = malloc(sizeof *var);

    *var = 10;
    printf("%s: %d (%p)\n", __func__, *var, (void *)var);
    pthread_create(&tid, NULL, th4, var);
    if (join)
    {
        pthread_join(tid, NULL);
        free(var);
    }
    /* else leak memory for var */
    pthread_exit(NULL);
}

int main(int argc, char **argv)
{
    pthread_t t1;
    pthread_t t3;

    if (argc > 1 && argv[argc] == NULL)
        join = 0;
    printf("%s pthread_join() on sub-threads\n", join ? "Using" : "Not using");

    printf("launch 1\n");
    pthread_create(&t1, NULL, th1, NULL);
    pthread_join(t1, NULL);

    printf("launch 3\n");
    pthread_create(&t3, NULL, th3, NULL);
    pthread_join(t3, NULL);

    printf("finished\n");

    return 0;
}

It is set up so that if a command line argument is passed, the sub-threads th1() and th3() do not do pthread_join() before exiting; if no argument is passed, they do wait.

When compiled as pth19 and run (on a Mac running macOS 10.14.2 Mojave, using GCC 8.2.0), I get:

$ pth19
Using pthread_join() on sub-threads
launch 1
th1: 10 (0x70000bda2f04)
th2: 10 (0x70000bda2f04)
0xA
launch 3
th3: 10 (0x7fa0a9500000)
th4: 10 (0x7fa0a9500000)
0xA
finished
$ pth19 1
Not using pthread_join() on sub-threads
launch 1
th1: 10 (0x70000690ff04)
Segmentation fault: 11
$

When used with the pthread_join() calls, it works correctly and as expected.

When the joins are omitted, the code crashes — which is one way 'undefined behaviour' manifests itself. When you don't join the th2 and th4 threads, the th1 and th3 threads can leave the others accessing data that's no longer valid. (Granted, the allocated memory wasn't freed in the original, but the crash was happening before the memory allocation.)

Be careful to ensure that the threads only access valid data.

Do not try sharing data between threads like this; you're taking a job that's already hard (thread programming correctly is hard) and making it even harder.

Upvotes: 4

anand
anand

Reputation: 163

Variables on thread stack are not accessible across threads or process. Within thread it can be passed as parameters to a function but once thread exits, its stack is gone and so the variables. In the code while pointer to variable is passed to second thread, once first thread exits, that variable is undefined for second thread.

Upvotes: -1

Related Questions