线程与其父堆栈之间的关系是什么?

时间:2017-03-31 09:14:26

标签: c linux multithreading pthreads

pthreads都有自己的堆栈并共享堆。它们在创建时继承父级的各个方面(sig mask,fpenv等)。关于子线程和父堆栈之间的关系似乎很少说。

那么我可以通过pthread_create将父堆栈上的某些东西的地址传递给子线程并期望这个工作吗?子线程是否能够看到父进程的堆栈?子帧堆栈是否在创建时填充了父堆栈的副本?

了解标准所保证的内容以及实际可行的内容对我有用。

4 个答案:

答案 0 :(得分:2)

  

那么我可以通过pthread_create将父堆栈上某些东西的地址传递给子线程并期望这个工作吗?

只有当您确保父堆栈上的某些内容在子范围内时,子线程才可以访问它。

例如,假设您有一个使用固定数量的线程来执行某项任务的函数。您可以存储描述父堆栈上每个线程的工作的结构,例如,如果父线程在线程的生命周期内保持在相同的范围(比如函数)中。

这可能听起来很简单,但它很容易出现bug(类似于free-after-free,除非此处 free 部分有变量或对象被传递出来范围)。

通常,为每个线程分别动态分配描述工作的结构要容易得多,而不是使用局部变量(数组)。它可以让你做像

这样的事情
struct thread_work {
    struct thread_work *next;    /* Singly linked list */
    pthread_t           id;      /* Thread ID */
    /* Threads may NOT change the above, shouldn't even access them */

    /* Stuff that describes the work each thread should do */
};

void cancel_threads(struct thread_work *list)
{
    while (list) {
        struct thread_work *curr = list;

        list = list->next;

        curr->next = NULL;
        if (!pthread_cancel(curr->id))
             pthread_join(curr->id, NULL);
        free(curr);
    }
}

struct work_item *create_threads(void *(*worker)(struct work_item *),
                                 size_t count,
                                 size_t stacksize)
{
    struct work_item *list = NULL;
    struct work_item *curr;
    pthread_attr_t    attrs;
    int               result;

    pthread_attr_init(&attrs);
    if (stacksize > 0)
        pthread_attr_setstacksize(&attrs, stacksize);

    while (n-->0) {

        curr = malloc(sizeof *curr);
        if (!curr) {
            cancel_threads(list);
            pthread_attr_destroy(&attrs);
            errno = ENOMEM;
            return NULL;
        }

        /* TODO: Set up work-specific fields in curr */


        /* Chain items into an easily managed linked list */
        curr->next = list;
        list = curr;

        /* Create the worker thread */
        result = pthread_create(&(curr->id), &attrs,
                                (void *(*)(void *))worker, 
                                curr);
        if (result) {
            cancel_threads(list);
            pthread_attr_destroy(&attrs);
            errno = result;
            return NULL;
        }
    }

    pthread_attr_destroy(&attrs);

    errno = 0;
    return list;
}

一般来说,很少有理由使用本地(堆栈)变量而不是动态分配的变量作为线程参数。

  

子线程是否能够看到父进程的堆栈?

进程是线程所属的实体。你叫什么"父进程"只是最初的主题。在POSIX(pthreads)中,最初的线程没有特殊的属性,除了它是进程中唯一的属性,最初。

线程可以查看属于该进程的所有内存,包括堆栈区域。但是,它很少有用,因为访问属于另一个线程的堆栈的线程只有在确保(使用例如互斥锁)将检查其堆栈的线程在适当的范围内(运行特定的函数或代码)时才有意义

一般情况下,您应该将堆栈视为用于内部簿记的内存翻转区域,其中局部变量仅在范围内的短时间内存在;因此完全不适合跨线程数据共享。

  

在创建时,子堆栈是否填充了父堆栈的副本?

没有。也就是说,你不能认为发生了这种情况。

(虽然有些架构或操作系统可能会这样做,但我不知道当前支持pthread的任何架构或操作系统实际上是这样做的。)

答案 1 :(得分:1)

鉴于适当的寿命,当然。

非常清楚,在其他线程完成之前允许在父级中释放基于堆栈的结构是一个坏主意:(

否则,没有实际问题。这是一个过程,因此根本没有可访问性问题。这种情况并非罕见,而且本身并不是坏事。

在任何特定情况下,它是否是一个好主意,由您自行决定和设计:)

如果将此类做法留给池和/或应用程序生命周期线程(即,那些从未明确终止/销毁,直到操作系统将其作为进程终止将其终止),这是最简单的。这可以确保在释放任何内存之前停止所有进程线程。

答案 2 :(得分:1)

那么我可以通过pthread_create将父堆栈上的某些内容的地址传递给子线程并希望这可以工作吗?

是。您可以在父线程的堆栈上传递某些内容的地址,并期望它能够正常工作。但是,请注意,对于任何线程,堆栈仅在线程处于活动状态时才处于活动状态。因此,如果您的程序即使在父级不再存在后子线程也将处于活动状态,那么您就会遇到麻烦。在这种情况下,期待未定义的行为。

子线程是否能够看到父进程的堆栈?

即使内存来自父级的堆栈,子级也能够访问通过pthread_create传递的内存。但是,如果它没有通过pthread_create传递,我认为它不会以其他方式访问。

在创建时,子堆栈是否会填充父堆栈的副本?

没有。没有父堆栈的副本传递给子。

答案 3 :(得分:1)

  

那么我可以通过pthread_create将父堆栈上的某些东西的地址传递给子线程并期望这个工作吗?子线程是否能够看到父进程的堆栈?

实际上是的。几乎每个pthreads实现都支持它。如果“父”线程在新创建的线程之前未完成,该线程可能仍在使用来自父级的堆栈地址,则可以。但该标准实际上并不是要求它。将堆栈地址(自动变量的地址)从“父”线程传递到新创建的线程称为实现定义。因此,绝对确定,您需要动态分配(使用malloc和朋友)并将其传递给pthread_create()

  

在创建时,子堆栈是否填充了父堆栈的副本?

这又是实现定义的。可以通过复制父堆栈或分配单独的堆栈来创建新线程。两者都是有效的,没有要求以这种或那种方式去做。

请参阅[pthread_create()] 1的基本原理部分以获取相关信息。