【问题标题】:How to pass a struct by value to a pthread?如何按值将结构传递给pthread?
【发布时间】:2016-09-02 21:10:49
【问题描述】:

所以我有一个多线程 C 程序,将在其中创建 N 个 pthread。我必须通过结构给线程一些参数。为了不必分配 N 结构,检查是否没有 malloc 错误,通过引用将它们传递给我的线程,然后释放结构数组,我想简单地创建一个临时结构和按值传递。这是一个演示问题的简单代码:

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

struct thread_arg {
    int value1;
    char value2;
    float value3;
};

void *foo(void *arg);

int main(int argc, char *argv[])
{
    int N = atoi(argv[1]);
    pthread_t *thread = (pthread_t *) malloc(N * sizeof(pthread_t));

    for (int i = 0; i < N; i++) {
        struct thread_arg arg;
        arg.value1 = i;
        arg.value2 = 'f';
        arg.value3 = i / 10;
        pthread_create(&thread[i], NULL, foo, arg);
    }

    free(thread);
    pthread_exit(NULL);
}

void *foo(void *arg)
{
    struct thread_arg my_arg = (struct thread_arg) arg;
    printf("%d%c%f\n", my_arg.value1, my_arg.value2, my_arg.value3);
    return NULL;
}

我知道将结构按值传递给期望它的函数是完全正常的,但是对于线程及其 NULL 指针,无论我进行何种类型的强制转换,我都会遇到错误。

【问题讨论】:

  • “你不能”可能是答案
  • 如果您要解决的问题需要创建多个线程,那么 malloc 结构并检查 malloc 是否失败并不是很大的成本。
  • 请注意,发布的代码有问题,因为有可能(确实很可能)当 foo() 函数在子线程中执行时,“arg”结构将不再存在于主线程的堆栈。 (正如其他人所说,修复是动态分配结构;这样线程可以通过在使用完成时释放它来控制结构的生命周期)
  • 在未先检查argc 以确保命令行参数确实存在之前,请勿引用argv[0] 之外的内容。
  • 谢谢你们所有的cmets!

标签: c multithreading pthreads


【解决方案1】:

确实可以将结构值按值传递给函数。但是pthread_create() 需要一个 pointer 作为参数(将传递给线程函数)。所以不能传值。

我建议malloc'ing N 个结构值并向每个线程传递一个单独的指针,您可以将它们从线程函数本身中释放出来:

int main(int argc, char *argv[])
{
    int N = atoi(argv[1]);
    pthread_t *thread = malloc(N * sizeof(pthread_t));

    for (int i = 0; i < N; i++) {
        struct thread_arg arg;
        arg.value1 = i;
        arg.value2 = 'f';
        arg.value3 = i / 10.0;
        struct thread_arg *p = malloc(sizeof *p);
        *p = arg;
        pthread_create(&thread[i], NULL, foo, p);
    }

    free(thread);
    pthread_exit(NULL);
}

void *foo(void *arg)
{
    struct thread_arg my_arg = *(struct thread_arg*) arg;
    printf("%d%c%f\n", my_arg.value1, my_arg.value2, my_arg.value3);
    free(arg);
    return NULL;
}

为简洁起见,我省略了错误检查。但是您应该始终检查malloc() 的返回值是否失败。

另外,请注意该语句有整数除法 (i/10):

arg.value3 = i / 10;

这可能不是您想要的。您可以通过以下方式修复:

arg.value3 = i / 10.0;

【讨论】:

    【解决方案2】:

    我想简单地创建一个临时结构并按值传递它[给线程启动函数]。

    你不能。线程启动函数接受一个void * 类型的参数。您不能以任何合理的方式将结构的值转换为void *。如果void * 的大小至少与您的结构表示的大小一样大,您可能能够解决该问题,但您似乎并非如此。

    相反,我建议创建一个包含尽可能多的结构的 自动 数组,然后向每个线程传递一个指向它自己的其中一个的指针。即使您在运行前不知道需要多少个 VLA,您也可以这样做:

    #define THREAD_LIMIT 50
    
    int main(int argc, char *argv[])
    {
        int N;
    
        if (argc < 2) {
            // handle too few arguments ...
            exit(1);
        }
    
        N = atoi(argv[1]);
        if (N < 0 || N > THREAD_LIMIT) {
            // handle invalid argument ...
            exit(1);
        } 
    
        struct thread_arg args[N];
        pthread_t threads[N];
    
        for (int i = 0; i < N; i++) {
            args[i].value1 = i;
            args[i].value2 = 'f';
            args[i].value3 = i / 10;
    
            if (pthread_create(&threads[i], NULL, foo, &args[i])) {
                // handle thread creation failure ...
            }
        }
    
        // did you forget to pthread_join() your threads?
        for (int i = 0; i < N; i++) {
            pthread_join(threads[i], NULL);
        }
    
        pthread_exit(NULL);
    }
    

    请注意,您可以对 pthread_t 数组执行相同的操作,如上所示,这样您就无需手动释放内存。

    如果您希望能够容纳如此大的线程数,以至于 VLA 可能会耗尽可用的堆栈空间,或者如果您需要在不支持 C99(例如 MSVC++)的编译器上工作,那么您可以动态分配整个 arg 结构数组,而不是单独分配每个结构,就像您在原始代码中为线程句柄所做的那样。

    无论您使用 VLA 还是动态分配的数组或单独的动态分配的结构,线程当然有必要在其生命周期结束后不尝试访问这些结构。当控制离开包含其声明的最内层块时,VLA 的生命周期结束;在上面的例子中,这将是主线程退出的时候。动态分配的对象的生命周期在它被释放时结束,除非程序先退出。

    因此,如果您打算让任何线程在主线程退出后继续运行,则 VLA 选项不适用,但如果您从不释放分配的内存,或者如果您小心,动态分配仍然可以工作在它被释放时进行编排。另一方面,正如我在示例中添加的那样,您可以通过在退出之前将其所有子线程 at join 来轻松地保留主线程。

    请注意,顺便说一句,这些方法不会比按值传递结构消耗更多的内存,如果可以这样做的话,因为根据定义,按值传递结构会复制它们.

    补充说明:

    • 请检查程序参数,包括数字和值
    • 请务必检查函数调用的返回值,至少检查它们的不同之处。因此,您应该检查pthread_create(),但在这种情况下您可能不需要检查pthread_join()
    • 如果它确实加入了所有子线程,则主线程可以执行普通返回或exit() 或reach-the-ending-},而不是pthread_exit()

    【讨论】:

    • 一般来说,使用 VLA 是一个非常糟糕的主意,而且您根本没有解决生命周期的控制问题。如果结构的生命周期在线程完成使用它们之前结束,程序将有未定义的行为。
    • @R..:我认为你对 VLA 过于消极了。有关于缺少连接的 cmets。并且应该在原始代码和这个答案中对 N 进行错误检查(以及目前包括代码的每个其他答案),检查 N 既不是零也不是负数,也不是对于某些不确定的“太大”值而言太大。
    • @JonathanLeffler 我不会说“缺少连接”。 OP 从主线程调用pthread_exit() 的事实表明他对加入不感兴趣。 pthread_join() 是一个 convenience 函数——在 pthread 中绝不是必需的。 R.. 对 VLA 的 cmet 可能是主观的(尽管我个人同意他们的观点),但他对未解决的生命周期的评论是有效的。
    • 答案已更新以解决对象生命周期,无论使用 VLA 还是动态分配都适用。至于加入子线程,我承认通常不是必须这样做的,但在这种情况下,OP 确实会不厌其烦地跟踪所有线程 ID,而没有其他明显的原因。有了这些,就可以在主线程退出之前轻松执行所需的线程连接,而是否应该这样做主要是风格、偏好和不同形式的便利性问题。
    • @usr:如果他不想加入线程,可以(也许应该)将它们设为分离线程。然而,这几乎无关紧要——是的,您必须考虑传递给pthread_create() 函数的参数的生命周期,并且您当然不能将结构按值传递给pthread_create(),除非结构足够小以适合void * (不是很大!)并且您准备好进行基本上不必要的铸造体操。如果你试图对抗系统,期待系统反击——pthread_create()(系统的相关位)期待指针。
    【解决方案3】:

    正如 usr 所写,您不能将结构按值传递给线程启动函数,因为pthread_create 的接口不利于这样做。通常最便宜的(就逻辑复杂性和性能而言)解决方案是使用malloc 并在不再需要结构时使用启动函数free,但如果有某种原因你真的不想使用malloc,你可以在结构体中放一个sem_t 信号量,在它读完之后让线程启动函数sem_post 它,并让调用pthread_create 的线程执行sem_wait。然后“父”线程控制结构的生命周期,因此它可以安全地在父线程中自动存储(“在堆栈上”)。

    【讨论】:

      【解决方案4】:

      在我写出明显的答案之前,你不能...

      ...但是...有一种有限的方式有可能

      如果您的整个结构都适合sizeof(void *),您可以通过值而不是通过引用传递该信息。

      在某些系统上,主要是嵌入式系统和遗留系统,sizeof(void *) 可能非常小(即 16 位)。在最近的系统中,您可以获得大量信息(大多数最近的系统使用 64 位)。

      在本例中,我将在结构中使用 32 位 to(我还将使用 union 代替,大多数是为了方便):

      #include "stdlib.h"
      #include "stdio.h"
      #include "stdint.h"
      #include "pthread.h"
      
      #if !defined(UINTPTR_MAX) || UINTPTR_MAX < UINT32_MAX
      #error Not enough space in a pointer for what we need or missing information.
      #endif
      
      union thread_arg {
        void *to_arg;
        struct {
          uint16_t value1;
          uint8_t value2;
          uint8_t value3;
        } data;
      };
      
      void *foo(void *arg);
      
      int main(int argc, char *argv[]) {
        if (argc < 2)
          fprintf(stderr, "Error: Provide N\n\n"), exit(1);
        int N = atoi(argv[1]);
        if (N < 0 || N > UINT16_MAX)
          fprintf(stderr, "Error: N should be 0-%u\n\n", UINT16_MAX), exit(1);
      
        pthread_t *thread = malloc(N * sizeof(pthread_t));
      
        union thread_arg thread_arg;
        for (int i = 0; i < N; i++) {
          thread_arg.data.value1 = (uint16_t)i;
          thread_arg.data.value2 = 'f';
          thread_arg.data.value3 = (uint16_t)i; // whatever...?
          if (pthread_create(thread + i, NULL, foo, thread_arg.to_arg))
            fprintf(stderr, "Couldn't initiate thread no. %i\n", N), exit(1);
        }
      
        for (int i = 0; i < N; i++)
          pthread_join(thread[i], NULL);
      
        free(thread);
        pthread_exit(NULL);
      }
      
      void *foo(void *arg) {
        union thread_arg thread_arg = {.to_arg = arg};
        printf("%d%c%f\n", thread_arg.data.value1, thread_arg.data.value2,
               (float)thread_arg.data.value3 / 10);
        return NULL;
      }
      

      锁好!

      【讨论】:

      • 投反对票是因为...?真的,人们,如果你不解释一票否决,那如何学习?
      • 嗯,这是一个有趣的 hack。感谢您演示如何操作。
      • @FredericoOliveira,不客气,这就是我们来这里的目的。通常我们不会在 SO 上互相感谢,我们简单地投票并接受对我们有帮助的好答案 - 否则,我们会用感谢信填满网站:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-01
      • 2013-03-29
      • 1970-01-01
      相关资源
      最近更新 更多