如何在C

时间:2019-01-07 15:48:57

标签: c multithreading memory stack heap

我试图了解线程之间如何共享内存。

我知道每个线程都有自己的堆栈,而每个线程之间共享堆。每个线程共享公共的寻址空间,因此另一个线程可以使用指针看到一个线程内的局部变量。这是通过在Linux中使用POSIX库pthread完成的。

因此,假设这是正确的,如果我创建了一个在其堆栈中分配了本地var的线程,则如果包含var的堆栈帧被破坏,则另一个线程应读取错误的值。有了这段代码,它便可以这种方式工作。

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);
}

但是,如果我使用malloc创建var以便在堆中分配它,那么它不会显示正确的值。为什么?代码在下面

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);
}

3 个答案:

答案 0 :(得分:3)

这是一个非常不小的最小MCVE(Minimal, Complete, Verifiable Example)程序,它基于问题中显示的内容:

#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;
}

它被设置为如果传递了命令行参数,则子线程th1()th3()在退出前不会执行pthread_join();如果没有传递任何参数,他们会等待。

当编译为pth19并运行时(在运行MacOS 10.14.2 Mojave的Mac上,使用GCC 8.2.0),我得到:

$ 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
$

pthread_join()调用一起使用时,它可以正常工作,并且符合预期。

当省略联接时,代码崩溃—这是“未定义行为”表现出来的一种方式。当您不加入th2th4线程时,th1th3线程可以让其他线程访问不再有效的数据。 (当然,分配的内存没有在原始内存中释放,但是崩溃发生在内存分配之前。)

请注意确保线程仅访问有效数据。

请勿尝试在这样的线程之间共享数据;您正在从事一项本来就困难的工作(正确进行线程编程非常困难),并使工作变得更加艰辛。

答案 1 :(得分:0)

  

我知道共享堆时每个线程都有自己的堆栈   每个线程之间。每个线程共享公共寻址空间,因此   一个线程内的局部变量可以被另一个线程使用   指针。这是通过在Linux中使用POSIX库pthread完成的。

其中一些细节可能随操作系统和线程实现的不同而有所不同,但是POSIX会specify这样做

  

其地址可能由线程确定的任何事物,包括但   不限于静态变量,通过malloc()获得的存储,   通过实现定义获得的直接可寻址存储   函数, 和自动变量 ,可供所有线程访问   同样的过程。

(添加了重点)。

  

如果我创建一个线程并在其堆栈中分配了本地变量,则另一个   如果包含var的堆栈帧是   毁了。

不,您几乎倒退了。您可以说的是,任何线程仅在该变量的生命周期内才被允许读取该变量的值。 C规范根本没有提到堆栈,但是在基于堆栈的实现中,当弹出其所属的堆栈帧时,或更早自动变量的生存期结束。变量生命周期结束后,尝试通过指针读取其值将产生未定义的行为。在可能表现出的多种可能的行为中,任何值都可能被读取,包括变量在其生命周期结束时持有的值。

  

但是,如果我使用malloc创建var以便在堆中分配它,那么它不会显示正确的值。为什么?

您还没有提供完整的示例,但是当我将您提供的功能与此main()结合时:

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

,结果程序已打印

  

0xa

这表明第二个线程确实确实在正确地读取了存储在分配的对象中的值,这应该在程序终止之前运行。

存在sleep()中的main()是为了使(很不确定)整个程序在第二个线程运行完成之前没有终止。实际上,应该确定要加入每个线程,但是原始功能无法实现这一点,因此我选择不对其进行修改。

答案 2 :(得分:-1)

无法跨线程或进程访问线程堆栈上的变量。可以在线程内将其作为参数传递给函数,但是一旦线程退出,它的堆栈就消失了,变量也就消失了。在指向变量的指针传递到第二个线程的代码中,一旦第一个线程退出,该变量就不会为第二个线程定义。