【问题标题】:What is the relationship between a thread and its parent stack?线程和它的父栈是什么关系?
【发布时间】:2017-08-25 12:01:41
【问题描述】:

pthread 都有自己的堆栈并共享堆。它们在创建时继承父级的各个方面(sig 掩码、fpenv 等)。子线程和父栈的关系好像很少说。

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

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

【问题讨论】:

    标签: c linux multithreading pthreads


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 有助于了解标准对此有何规定。了解定义的实现和保证的内容对我很有帮助。
    【解决方案2】:

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

    仅当您确保父堆栈上的内容在子线程可能访问它的范围内时。

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

    这听起来可能很简单,但它很容易产生错误(有点类似于 use-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) 中,初始线程除了最初是进程中唯一的一个之外没有其他特殊属性。

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

    一般来说,您应该将堆栈视为用于内部簿记的内存搅动区域,其中局部变量仅在其作用域内的短时间内存在;因此完全不适合跨线程数据共享。

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

    没有。也就是说,您不能假设会发生这种情况。

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

    【讨论】:

      【解决方案3】:

      考虑到适当的生命周期,当然。

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

      否则,没有实际问题。这都是一个过程,因此根本没有可访问性问题。这并不少见,而且本身也不是不好的做法。

      在任何特定情况下是否是一个好主意,由您来确定和相应地设计:)

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

      【讨论】:

        【解决方案4】:

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

        是的。您可以传递父线程堆栈上某些东西的地址并期望它工作。但是,请注意,对于任何线程,堆栈仅在线程处于活动状态时才处于活动状态。因此,如果您的程序即使在父线程不存在后子线程仍处于活动状态,那么您就有麻烦了。在这种情况下会出现未定义的行为。

        子线程能看到父进程的栈吗?

        即使内存来自父级的堆栈,子级也可以访问通过 pthread_create 传递的内存。但是,如果它不通过 pthread_create 传递,我认为它不会有访问权限。

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-31
          • 2011-12-28
          • 1970-01-01
          • 2011-05-21
          • 1970-01-01
          相关资源
          最近更新 更多