12

C语言陷阱与技巧第46节,如何获取线程函数的执行结果?怎么知道线程函数是成功了还是...

 3 years ago
source link: https://blog.popkx.com/c%E8%AF%AD%E8%A8%80%E9%99%B7%E9%98%B1%E4%B8%8E%E6%8A%80%E5%B7%A7%E7%AC%AC46%E8%8A%82-%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96%E7%BA%BF%E7%A8%8B%E5%87%BD%E6%95%B0%E7%9A%84%E6%89%A7%E8%A1%8C/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在本专栏的第41节,我们介绍了C语言程序开发中,有些任务可以放在后台运行,以避免主逻辑阻塞,造成C语言程序进入“未响应”等假死状态,影响用户使用体验。

获取线程函数的返回值

在C语言程序开发中,需要放在后台运行的任务通常以线程的形式实现,这一点我们在第 41 节已经举例较为详细的介绍过。不过,第 41 节的例子并未关心线程函数的返回值,而是将其设置为 detached ,以避免资源泄漏。

如果需要得到线程函数的返回值,该怎么做呢?线程函数是后台运行的,乍一看似乎连它什么时候结束都很难知道。

在第 41 节的例子中,主逻辑是通过 while(1) 死循环等待线程函数执行结束的,不过这只适合作为示例,实际的C语言程序开发中,通过死循环等待线程结束的做法肯定是不提倡的。

事实上,C语言的 pthread 库为我们提供的 pthread_join() 函数,就是专门等待线程函数结束,并且获取其返回值的。请看下面这段C语言代码示例:

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

void delay()
{
    unsigned long cnt = 999999;
    while(cnt--);
}

void *thread_1(void *p)
{
    int cnt = 10;
    while(cnt--){
        delay();
        printf("thread_1 is running, cnt: %d\n", cnt);
    }
    return NULL;
}

int main()
{
    pthread_t pid;
    pthread_create(&pid, NULL, thread_1, NULL);

    printf("\nmain is running...\n\n");

    void *ret;
    pthread_join(pid, &ret);

    printf("thread exit with ret: %p\n", ret);

    return 0;
}
d401036219f790213f5e8fa3dc98cea5.png

为了尽量简洁,便于讨论主题,示例C语言代码没有做错误处理。

上面这段C语言代码很简单,线程函数 thread_1() 打印 10 次 "thread_1 is running...”, main() 函数创建线程后,调用 pthread_join() 函数等待线程函数结束,并接收其返回值。编译并执行上述C语言代码,得到如下输出:

# gcc t.c -lpthread
# ./a.out 

main is running...

thread_1 is running, cnt: 9
thread_1 is running, cnt: 8
thread_1 is running, cnt: 7
thread_1 is running, cnt: 6
thread_1 is running, cnt: 5
thread_1 is running, cnt: 4
thread_1 is running, cnt: 3
thread_1 is running, cnt: 2
thread_1 is running, cnt: 1
thread_1 is running, cnt: 0
thread exit with ret:  (nil)

可见,pthread_join() 函数的确可以使 main() 等到线程函数 thread_1() 完成退出,根据上面的输出内容,我们也可以知道 thread_1() 函数返回了 NULL。读者感兴趣的话,可以修改 thread_1() 的返回值,例如:

void *thread_1(void *p)
{
    ...
    return 8;
}
25541152fd8aedfc30d32975e380d192.png

编译修改后的C语言代码并执行,得到如下输出:
...
thread exit with ret: 0x8

与预期一致。

调用pthread_join()后,线程函数还会“泄漏”资源吗?

之前的文章里说到,在C语言程序开发中创建线程函数,如果不做恰当处理,该线程函数使用的栈等资源无法被系统回收,会造成“资源泄漏”。久而久之,就可能导致整个C语言程序崩溃。

线程函数运行完毕后,系统不回收它的资源,是因为系统不能确定是否仍然有程序关心该线程函数的执行结果。在第 41 节中,我们调用 pthread_detach() 函数告诉系统:没有人关心该线程函数的返回值。所以线程执行完毕后,系统就立刻回收它使用的资源了。

需要说明的是,因为 main() 函数调用 pthread_join() 接收了线程函数 thread_1() 的返回值,也即“有人关心”thread_1() 的执行结果,所以不允许再为 thread_1() 函数设置 detached 标志,这一点要注意。

相对的,如果某个线程函数被设置为 detached,就不能再调用 pthread_join() 等待接收其返回值了。也就是说,在C语言程序开发中,pthread_detach() 和 pthread_join() 二者只能取其一。

可能读者有疑问了,如果C语言程序调用了 pthread_join(),那线程函数不是 detached,会造成“资源泄漏”吗?得到答案最直接的办法就是写代码测试,我们修改 main() 函数的C语言代码:

int main()
{
    int i;

    pthread_t pid[10];
    for(i=0; i<10; i++)
        pthread_create(pid+i, NULL, thread_1, NULL);

    printf("\nmain is running...\n\n");

    void *ret;
    for(i=0; i<10; i++)
        pthread_join(pid[i], &ret);

    printf("thread exit with ret: %p\n", ret);

    return 0;
}
1c50fcbec3a97e0b5e4a261f91a5c062.png

上述 main() 函数创建了 10 个线程,并且分别调用 pthread_join() 等待接收线程返回值。读者可自行编译执行这段修改后的C语言代码,像第 41 节一样使用 top 命令对比资源使用情况,应该能够发现是没有资源泄漏的。

这是肯定的,之前系统不回收执行完毕的线程资源,是因为系统不能确定是否仍然有程序关心线程的执行结果,现在 main() 函数调用 pthread_join() 获取了线程的执行结果,系统知道 main() 函数处理了线程的执行结果,于是就可以放心的把资源回收了。

值得说明的是,上述 main() 函数的C语言代码,有些可能会写成下面这样,请看:

int main()
{
    int i;
    pthread_t pid;
    void *ret;
    for(i=0; i<10; i++){
        pthread_create(&pid, NULL, thread_1, NULL);
        pthread_join(pid, &ret);
    }
    ...
    return 0;
}

这其实是一个陷阱,初学者很容易跳进去。这段C语言代码有什么问题呢?其实很明显,pthread_join() 函数会等待线程函数执行完毕,然后获取其返回值,也就是说,上面这段C语言代码并不会创建 10 个同时(假设平台10核以上)运行的线程,而是每创建一个线程,就等待其执行完毕,再创建下一个。

本节主要讨论了C语言程序开发中,获取线程返回值的方法。其实就是 pthread_join() 函数的应用,读者应小心文章最后讨论的“陷阱”。pthread_join() 函数也可以通知系统在线程运行完毕后回收其资源,避免泄漏。但是应该注意,在C语言程序开发中,pthread_detach() 和 pthread_join() 二者只能取其一,实际上,在创建线程后,必须从二者中选择一个:如果主逻辑不关心线程何时结束,也不关心其执行结果,则应该将其设置为 detached。否则,就该调用 pthread_join() 了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK