再论线程返回值
int pthread_join(pthread_t thread, void** retval);
- 等待的线程未退出,则调用线程陷入阻塞状态
- 等待的线程已退出,则调用线程将获得线程返回值
pthread_join() VS wait()
进程之间的等待只能是父进程等待子进程
线程之间都可以进行 等待 / 连接
wait() 等待任意子进程退出,pthread_join 必须指定目标线程
pthread_join() 等待的线程必然是 pthread_create() 创建的线程
下面的程序输出什么?为什么?
多个线程同时 join 一个线程实验
test-1.c
#define _GNU_SOURCE /* To get pthread_getattr_np() declaration */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <sys/syscall.h>
pid_t gettid(void)
{
return syscall(SYS_gettid);
}
void* thread_entry(void* arg)
{
pid_t pt = gettid();
int i = 0;
int n = (long long)arg;
for(i=0; i<n; i++)
{
printf("thread(%d): i = %d\n", pt, i);
sleep(1);
}
return (void*)pt;
}
void* wait_entry(void* arg)
{
pid_t pt = gettid();
pthread_t t = (pthread_t)arg;
int r = 0;
sleep(1);
printf("tid = %d\n", pt);
r = pthread_join(t, NULL);
printf("thread(%d) r = %d\n", pt, r);
return NULL;
}
int main()
{
int r = 0;
void* tr = NULL;
pthread_t t = 0;
pthread_t wt = 0;
printf("main(%d)\n", getpid());
pthread_create(&t, NULL, thread_entry, (void*)5);
pthread_create(&wt, NULL, wait_entry, (void*)t);
pthread_create(&wt, NULL, wait_entry, (void*)t);
while(1)
{
sleep(6);
}
return 0;
}
该示例中创建了 3 个线程,其中有 2 个线程会同时 join 另一个相同的线程
程序运行结果如下图所示:
通过打印可以看出,tid 为 19585 的线程 join 另一个线程成功了,而 tid 为 19584 的线程卡死在 pthread_join() 接口,一直无法返回
多个线程同时 join 另一个相同的线程,只能有一个线程 join 成功,其余的线程会卡死
注意
多个线程同时 join 到同一个线程时,行为是未定义的!
再论 wait() / waitpid() 的意义
子进程 退出 / 终止 后,部分系统资源会暂留 (为父进程提供重要信息)
父进程调用 wait() / waitpid(),为子进程 "收尸" 处理并释放暂留资源
如果子进程得不到回收,那么可能影响正常进程的创建 (系统资源无法释放)
思考
子线程结束执行,是否意味着系统资源被释放?
下面程序中变量 r 的值输出什么?为什么?
线程资源回收实验
test-2.c
#define _GNU_SOURCE /* To get pthread_getattr_np() declaration */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <sys/syscall.h>
pid_t gettid(void)
{
return syscall(SYS_gettid);
}
void* thread_entry(void* arg)
{
pid_t pt = gettid();
int i = 0;
int n = (long long)arg;
for(i=0; i<n; i++)
{
printf("thread(%d): i = %d, &i = %p\n", pt, i, &i);
sleep(1);
}
return (void*)(long)pt;
}
int main()
{
int r = 0;
void* tr = NULL;
pthread_t t = 0;
pthread_t wt = 0;
printf("main(%d)\n", getpid());
pthread_create(&t, NULL, thread_entry, (void*)5);
sleep(6);
// r = pthread_join(t, &tr);
pthread_create(&t, NULL, thread_entry, (void*)5);
printf("r = %d, tr = %ld\n", r, (long)tr);
sleep(10);
return 0;
}
该程序会先创建一个线程去执行 pthread_entry(),然后等这个线程执行结束后,再次创建一个线程去执行 pthread_entry()
pthread_entry() 会去打印线程栈中 i 的值和地址
程序运行结果如下图所示:
两个线程打印出来 i 的地址是不同的
将第 44 行的 pthread_join() 注释打开,查看运行结果
两个线程打印出来 i 的地址是相同的,说明了 pthread_join() 除了阻塞等待线程结束、获取线程结束返回值,还会释放线程所占用的资源
第 44 行,通过 pthread_join(),成功释放了第一个线程所占用的系统资源,创建出来的第二个线程直接复用了第一个线程所使用的线程栈,所以打印出来 i 的地址是相同的;如果不调用 pthread_join(),那么线程所占用的资源会得不到清理
实验结论
默认情况下,Linux 线程退出后不会主动释放系统资源
pthread_join() 除了等待线程执行结束,还会释放线程所占用的资源
当创建的线程过多且没有等待操作,可能造成系统资源严重泄漏
任意线程均可释放其它已经退出线程的系统资源
线程的分离
默认情况下,新创建的线程处于可连接状态 (Joinable)
可连接的线程退出后需要执行连接操作,否则线程资源无法释放
实际需求:
- 线程不需要返回值,那么退出后就不应该需要连接
- 线程退出则自动释放资源,不需要连接操作
不可连接线程 (分离线程)
在创建线程时,通过属性参数指定线程为 "分离状态"
- int pthread_attr_setdetachstate(pthread_attr_t* attr, int detachstate);
- int pthread_attr_getdetachstate(const pthread_attr_t* attr, int* detachstate);
通过 pthread_detach() 函数设置线程为 "分离状态"
- int pthread_detach(pthread_t thread);
下面的程序是否会有内存泄漏?
线程分离实验
test-3.c
#define _GNU_SOURCE /* To get pthread_getattr_np() declaration */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <sys/syscall.h>
pid_t gettid(void)
{
return syscall(SYS_gettid);
}
void* thread_entry(void* arg)
{
pid_t pt = gettid();
int i = 0;
int n = (long long)arg;
for(i=0; i<n; i++)
{
printf("thread(%d): i = %d, &i = %p\n", pt, i, &i);
sleep(1);
}
pthread_detach(pthread_self());
return NULL;
}
int main()
{
int r = 0;
pthread_t t = 0;
printf("main(%d)\n", getpid());
pthread_create(&t, NULL, thread_entry, (void*)5);
sleep(10);
r = pthread_join(t, NULL);
printf("r = %d\n", r);
return 0;
}
主线程创建了一个线程去执行 pthread_entry(),然后休眠 10s 后,去连接这个创建出来的线程
pthread_entry() 中通过 pthread_detach() 将自身设置为分离线程
程序运行结果如下图所示:
通过打印可以看出,无法去连接一个分离状态的线程
test4.c
#define _GNU_SOURCE /* To get pthread_getattr_np() declaration */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>
#include <sys/syscall.h>
pid_t gettid(void)
{
return syscall(SYS_gettid);
}
void* thread_entry(void* arg)
{
pid_t pt = gettid();
int i = 0;
int n = (long long)arg;
for(i=0; i<n; i++)
{
printf("thread(%d): i = %d, &i = %p\n", pt, i, &i);
sleep(1);
}
pthread_detach(pthread_self());
return NULL;
}
int main()
{
pthread_t t = 0;
printf("main(%d)\n", getpid());
pthread_create(&t, NULL, thread_entry, (void*)5);
sleep(6);
pthread_create(&t, NULL, thread_entry, (void*)5);
sleep(6);
return 0;
}
主线程创建了两个线程,第二个线程等第一个线程运行结束后再创建出来
程序运行结果如下图所示:
两个子线程打印出来 i 的地址是相同的,说明线程设置为分离状态后,会在运行结束后释放线程资源,第二个线程的线程栈直接复用了第一个线程的线程栈
实验结论
线程进入分离状态,其它线程无法连接 (不可等待 且 无法获取返回值)
分离状态的线程退出后主动释放系统资源 (常规需求)
线程的分离状态指线程不可能执行连接操作 (并非脱离进程不可控)
在工程开发中,一定要做好线程设计 (线程间是否需要等待?)