如何在C语言中从pthread线程返回一个值?

74

我是C语言的新手,想尝试使用线程。我想使用 pthread_exit() 来从线程中返回某些值。

我的代码如下:

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

void *myThread()
{
   int ret = 42;
   pthread_exit(&ret);
}

int main()
{
   pthread_t tid;
   void *status;
 
   pthread_create(&tid, NULL, myThread, NULL);
   pthread_join(tid, &status);

   printf("%d\n",*(int*)status);   
 
   return 0;
}

我希望程序输出"42\n",但它输出了一个随机数。如何打印返回的值?

看起来问题出在我返回了一个局部变量的指针。在多线程中返回/存储变量的最佳实践是什么?使用全局哈希表吗?


1
对于编辑的回应:如果我需要为多个线程提供一个写入结果的位置,我倾向于使用数组。如果您事先不知道要创建多少个线程的上限,那么我通常会认为这是一个问题。但总的来说,只要启动线程的人可以确保有一个地方让线程存储其结果;线程被告知在哪里存储它;并且任何加入线程的人都可以恢复结果并在必要时释放它,那么任何结构都可以。如果线程以与作为参数传递的相同值退出,那会有所帮助。 - Steve Jessop
对我来说这很好用。在过去的12年里有些事情已经改变了。 - v.j
10个回答

83

这里是一个正确的解决方案。在这种情况下,tdata在主线程中分配,线程有一个空间来放置它的结果。

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

typedef struct thread_data {
   int a;
   int b;
   int result;

} thread_data;

void *myThread(void *arg)
{
   thread_data *tdata=(thread_data *)arg;

   int a=tdata->a;
   int b=tdata->b;
   int result=a+b;

   tdata->result=result;
   pthread_exit(NULL);
}

int main()
{
   pthread_t tid;
   thread_data tdata;

   tdata.a=10;
   tdata.b=32;

   pthread_create(&tid, NULL, myThread, (void *)&tdata);
   pthread_join(tid, NULL);

   printf("%d + %d = %d\n", tdata.a, tdata.b, tdata.result);   

   return 0;
}

9
这是这里最干净的示例 - 我很惊讶它没有得到更高的赞同。特别是它展示了管理调用层存储是正确的做法 - 并且指针可以用于传递更复杂的结构 - 无论是输入还是输出。 - Floris
1
free()和在for循环中等待的线程怎么办? - Acauã Pitta

70

您正在返回一个本地变量的地址,当线程函数退出时该变量已不存在。无论如何,为什么要调用pthread_exit?为什么不直接从线程函数中返回一个值呢?

void *myThread()
{
   return (void *) 42;
}

然后在主函数中:

printf("%d\n", (int)status);   

如果您需要返回一个复杂的值,比如一个结构体,最简单的方法可能是通过使用malloc()来动态分配内存并返回指针。当然,启动线程的代码将负责释放内存。

4
如果你实际上的意思是“为什么不”,那么为什么不的原因是把42转换成指针类型是未定义行为。当然,如果它会造成任何不良后果,那么这就是一个“这是C语言实现,吉姆,但我们不熟悉它”的时刻。 - Steve Jessop
7
pthread_exit类似于exit。它允许您的线程像程序一样提前退出,可以从线程中的任何代码调用它,而要返回,则必须返回到线程入口点。当然,不同之处在于,如果您尝试除了从入口点返回外的其他方式退出线程,很可能会在所有地方泄漏资源。 - Steve Jessop
5
将42强制转换为void*并不会导致未定义的行为,它是由具体实现定义的。而且在实际的实现中,它的定义与预期相符。 - R.. GitHub STOP HELPING ICE
2
结果可能是陷阱表示。我在两个方面都非常谨慎地避免陷阱。首先,我认为只要避免编写可能读取它们的代码就足够合理了,即使以标准允许的方式来看待也是如此,因为相关文本只提到lvalue。其次,我忽略了实际生活中没有任何一种实现定义其类型具有任何陷阱的事实,除了可能是浮点类型。从仅依赖于标准,通过常见实现实践,到会导致问题的错误假设的滑动比例上,两种方式都更加谨慎是一个相当安全的选择。 - Steve Jessop
1
在返回值时,将int强制转换为void*时要小心。我知道pthread的Xenomai实现将(void *)-2作为PTHREAD_CANCELED的返回值。 - DAhrens
显示剩余4条评论

28

你返回了一个指向本地变量的指针。即使没有涉及到线程,这也是不好的。

通常情况下,在启动和加入线程的线程相同时,应该将指向int的指针作为第四个参数传递给pthread_create,并由调用者管理其位置。然后,它就成为线程入口点的(唯一)参数。您可以使用线程退出值来指示成功:

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

int something_worked(void) {
    /* thread operation might fail, so here's a silly example */
    void *p = malloc(10);
    free(p);
    return p ? 1 : 0;
}

void *myThread(void *result)
{
   if (something_worked()) {
       *((int*)result) = 42;
       pthread_exit(result);
   } else {
       pthread_exit(0);
   }
}

int main()
{
   pthread_t tid;
   void *status = 0;
   int result;

   pthread_create(&tid, NULL, myThread, &result);
   pthread_join(tid, &status);

   if (status != 0) {
       printf("%d\n",result);
   } else {
       printf("thread failed\n");
   }

   return 0;
}

如果你绝对需要使用线程的退出值来构造一个结构体,那么你就必须动态地分配它(并确保加入线程的人释放它)。尽管如此,这并不是理想的做法。


3
我刚发现根据http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_exit.html,您不需要在线程的函数调用中使用pthread_exit。 “线程终止的正常机制是从在pthread_create()调用中指定的例程返回,该例程启动了它。 pthread_exit()函数提供了一种能力,使线程可以在不需要从该线程的启动例程返回的情况下终止,从而提供类似于exit()的功能。”应该在需要早期终止时使用pthread_exit。 - Zachary Kraus
1
我会使用 fprintf 将错误打印到 stderr,而不是打印线程失败的消息。 - Box Box Box Box
void * status 与整数零进行比较会导致“警告:指针和整数之间的比较”。 - VeraKozya
@Steve Jessop 我认为 pthread_join(tid, &status); 不正确,因为 pthread_join 的第二个参数应该是一个双指针,如下所示:int pthread_join(pthread_t tid, void **thread_return); - user9623401
@amjoad:status 的类型为 void*,因此 &status 的类型为所需的 void** - Steve Jessop

10

我认为你需要将数字存储在堆上。变量int ret在栈上,并且在函数myThread执行结束时被销毁。

void *myThread()
{
   int *ret = malloc(sizeof(int));
   if (ret == NULL) {
       // ...
   }
   *ret = 42;
   pthread_exit(ret);
}

当你不需要它时,不要忘记释放它。

另一种解决方案是像Neil Butterworth建议的那样,将数字作为指针的值返回。


真是愚蠢,pthread_exit 只接受指针作为参数。 :( - Petr Peller
7
这并不是“愚蠢”,而是C类型系统的限制。为了通过pthreads系统返回任意类型的参数,你需要一个类似于Python的对象系统,其中变量没有类型,只有值,或者你需要大量使用C++模板技术。同样地,你只能从线程入口点返回 void*,而不能返回(例如)一个 double。 - Steve Jessop
1
还要记得检查malloc是否成功了 :-) - Cristian Ciupitu
总结一下:内存管理必须由子线程手动完成,子线程调用malloc在堆上分配一个值,并返回指向该值的指针。而父线程则使用pthread_join来获取该地址以访问相同的值,并在后续调用free进行清理。 - Felix Quehl
总结一下:内存管理必须由子线程手动完成,子线程调用malloc在堆上分配一个值,并返回指向该值的指针,而父线程则使用pthread_join来获取该地址以访问相同的值,然后在清理时调用free - undefined

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

void* myprint(void *x)
{
    int k = *((int *)x);
    printf("\n Thread created.. value of k [%d]\n", k);
    //k =11;
    pthread_exit((void *)k);
}

int main()
{
    pthread_t th1;
    int x =5;
    int *y;
    pthread_create(&th1, NULL, myprint, (void*)&x);
    pthread_join(th1, (void*)&y);
    printf("\n Exit value is [%d]\n", y);
}  

1
从线程函数返回本地变量的值,而不使用全局变量。 - Abhishek

2
#include <pthread.h>
#include <stdio.h>
#include <stdint.h>

void *myThread(void *args)
{
    return (void *)(intptr_t)42;
}

int main(void)
{
    pthread_t tid;
    void *status;
    int ret;

    pthread_create(&tid, NULL, myThread, NULL);
    ret = pthread_join(tid, &status);

    if (ret) {
        fprintf(stderr, "pthread_join() failed\n");
        return -1;
    }

    /* pthread_join() copies the exit status (namely 42) of the target thread
     * into the location pointed to by retval (namely &status), &status points
     * to void *, so we need to cast void * to int.
     */
    printf("%ld\n", (intptr_t)status);

    return 0;
}

需要手动释放status指针所引用的内存以防止内存泄漏吗?否则,返回值的内存释放是如何处理的? - Felix Quehl

2
您正在返回对堆栈上变量ret的引用。

2
问题:返回/存储多个线程变量的最佳实践是什么?全局哈希表?
这完全取决于你想要返回什么以及如何使用它。如果你只想返回线程的状态(比如说线程是否完成了它打算做的事情),那么只需使用pthread_exit或使用return语句从线程函数中返回值即可。
但是,如果你想要更多的信息用于进一步处理,那么可以使用全局数据结构。但是,在这种情况下,您需要使用适当的同步原语来处理并发问题。或者您可以分配一些动态内存(最好是为您想要存储数据的结构)并通过pthread_exit发送它,一旦线程加入,您就可以在另一个全局结构中更新它。这样只有一个主线程会更新全局结构,并且并发问题得到解决。但是,您需要确保释放不同线程分配的所有内存。

1

如果您不熟悉返回地址并且只有一个变量,例如要返回一个整数值,您甚至可以在传递之前将其强制转换为(void *),然后当您在主函数中收集它时,再将其强制转换为(int)。这样,您就可以获得该值而不会出现令人讨厌的警告。


将void*强制转换为int是实现相关的行为,不被标准定义。 - xdavidliu

0
我做了这个:
void *myThread()
{
   int ret = 42;
   return (void*)(__intptr_t)ret;
}

int main()
{
   pthread_t tid;
   int status = 1;
 
   pthread_create(&tid, NULL, myThread, NULL);
   pthread_join(tid, (void*)(__intptr_t)&status);

   printf("%d\n",status);   
 
   return 0;
}

感谢您为Stack Overflow社区做出的贡献。这可能是一个正确的答案,但如果您能提供代码的额外解释,让开发人员能够理解您的思路,那将非常有帮助。对于那些对语法不太熟悉或者难以理解概念的新手开发人员来说,这尤其有用。您是否可以友好地编辑您的答案,以便为社区的利益提供更多细节? - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接