线程的连接与分离

再论线程返回值

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 的地址是相同的,说明线程设置为分离状态后,会在运行结束后释放线程资源,第二个线程的线程栈直接复用了第一个线程的线程栈

实验结论

线程进入分离状态,其它线程无法连接 (不可等待 且 无法获取返回值)

分离状态的线程退出后主动释放系统资源 (常规需求)

线程的分离状态指线程不可能执行连接操作 (并非脱离进程不可控)

在工程开发中,一定要做好线程设计 (线程间是否需要等待?)

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值