Linux:线程创建与终止下(线程六)

一、核心结论

线程终止后需通过pthread_join(阻塞回收)或pthread_detach(自动回收)释放资源,避免僵尸线程。线程属性(如栈大小、分离状态)可通过pthread_attr_t设置,灵活适配不同场景需求。

二、线程等待:pthread_join 函数详解

1. 为什么需要线程等待?

  • 已退出的线程会残留退出状态和少量资源(如线程 ID),若不回收会成为 "僵尸线程",占用系统资源;
  • 主线程需通过等待获取线程的退出状态(返回值、是否被取消);
  • 避免主线程提前退出,导致子线程被强制终止。

2. 函数原型

复制代码
int pthread_join(pthread_t thread, void **retval);

3. 参数说明

  • thread:目标线程的 ID;
  • retval:输出参数,存储线程的退出状态:
    • 线程通过returnpthread_exit终止:*retval为返回值;
    • 线程被pthread_cancel取消:*retvalPTHREAD_CANCELED
    • 不关心退出状态可传 NULL。

4. 代码示例:获取不同终止方式的退出状态

复制代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

// 方式1:return终止
void* thread_return(void* arg) {
    printf("线程1:通过return终止\n");
    int* res = (int*)malloc(sizeof(int));
    *res = 10;
    return (void*)res;
}

// 方式2:pthread_exit终止
void* thread_exit(void* arg) {
    printf("线程2:通过pthread_exit终止\n");
    int* res = (int*)malloc(sizeof(int));
    *res = 20;
    pthread_exit((void*)res);
}

// 方式3:被cancel终止
void* thread_cancel(void* arg) {
    printf("线程3:循环执行,等待被取消\n");
    while (1) {
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t tid1, tid2, tid3;
    void* exit_status;
    int ret;

    // 线程1:return终止
    pthread_create(&tid1, NULL, thread_return, NULL);
    ret = pthread_join(tid1, &exit_status);
    if (ret == 0) {
        printf("回收线程1,退出状态:%d\n", *(int*)exit_status);
        free(exit_status); // 释放线程分配的内存
    }

    // 线程2:pthread_exit终止
    pthread_create(&tid2, NULL, thread_exit, NULL);
    ret = pthread_join(tid2, &exit_status);
    if (ret == 0) {
        printf("回收线程2,退出状态:%d\n", *(int*)exit_status);
        free(exit_status);
    }

    // 线程3:被cancel终止
    pthread_create(&tid3, NULL, thread_cancel, NULL);
    sleep(3); // 让线程运行3秒
    pthread_cancel(tid3);
    ret = pthread_join(tid3, &exit_status);
    if (ret == 0) {
        if (exit_status == PTHREAD_CANCELED) {
            printf("回收线程3,退出状态:PTHREAD_CANCELED\n");
        }
    }

    return 0;
}

5. 运行结果

复制代码
线程1:通过return终止
回收线程1,退出状态:10
线程2:通过pthread_exit终止
回收线程2,退出状态:20
线程3:循环执行,等待被取消
回收线程3,退出状态:PTHREAD_CANCELED

关键注意点:

  • pthread_join是阻塞函数,调用线程会暂停直到目标线程终止;
  • 线程的返回值若指向局部变量,会因栈销毁导致野指针,需用malloc分配或使用全局变量;
  • 不能等待已分离的线程,否则返回 EINVAL 错误。

三、线程分离:pthread_detach 函数详解

1. 适用场景

若不关心线程的退出状态,pthread_join的阻塞会成为负担,此时可将线程设置为 "分离状态",线程终止后自动释放资源,无需手动等待。

2. 函数原型

复制代码
int pthread_detach(pthread_t thread);

3. 两种分离方式

(1)其他线程为目标线程分离
复制代码
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
pthread_detach(tid); // 主线程为子线程分离
(2)线程自分离(推荐)
复制代码
void* thread_func(void* arg) {
    pthread_detach(pthread_self()); // 线程自身分离
    printf("分离线程执行中\n");
    sleep(2);
    return NULL;
}

4. 代码示例:分离线程的使用

复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

void* thread_func(void* arg) {
    pthread_detach(pthread_self()); // 自分离
    printf("分离线程启动,ID:%lu\n", (unsigned long)pthread_self());
    sleep(2);
    printf("分离线程执行完毕,将自动回收资源\n");
    return NULL;
}

int main() {
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, thread_func, NULL);
    if (ret != 0) {
        printf("创建线程失败:%s\n", strerror(ret));
        return -1;
    }

    sleep(1); // 等待线程完成分离(避免提前调用join)
    // 尝试等待分离线程,会返回错误
    ret = pthread_join(tid, NULL);
    if (ret != 0) {
        printf("等待分离线程失败:%s\n", strerror(ret));
    }

    sleep(2); // 等待线程执行完毕
    printf("主线程退出\n");
    return 0;
}

5. 运行结果

复制代码
分离线程启动,ID:140709316464384
等待分离线程失败:Invalid argument
分离线程执行完毕,将自动回收资源
主线程退出

核心区别:joinable vs 分离状态

特性 joinable(默认) 分离状态
资源回收 需调用 pthread_join 自动回收
阻塞性 调用线程阻塞 无阻塞
获取退出状态 可以 不可以
适用场景 需获取线程执行结果 后台服务线程

四、线程属性:pthread_attr_t 详解

线程属性用于定制线程的行为(如栈大小、调度优先级),需先初始化属性对象,设置属性后传入pthread_create

1. 核心属性操作流程

  1. 初始化属性:pthread_attr_init(&attr)
  2. 设置属性(如栈大小、分离状态);
  3. 创建线程时传入属性:pthread_create(&tid, &attr, func, arg)
  4. 销毁属性:pthread_attr_destroy(&attr)

2. 常用属性设置

(1)设置线程分离状态
复制代码
pthread_attr_t attr;
pthread_attr_init(&attr);
// 设置为分离状态(创建后无需join)
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, thread_func, NULL);
pthread_attr_destroy(&attr);
(2)设置栈大小
复制代码
size_t stack_size = 1024 * 1024; // 1MB
pthread_attr_setstacksize(&attr, stack_size);

3. 代码示例:使用线程属性创建分离线程

c

运行

复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* thread_func(void* arg) {
    printf("属性线程启动,ID:%lu\n", (unsigned long)pthread_self());
    sleep(2);
    printf("属性线程执行完毕\n");
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_attr_t attr;
    int ret;

    // 初始化属性
    ret = pthread_attr_init(&attr);
    if (ret != 0) {
        printf("初始化属性失败:%s\n", strerror(ret));
        return -1;
    }

    // 设置为分离状态
    ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (ret != 0) {
        printf("设置分离状态失败:%s\n", strerror(ret));
        return -1;
    }

    // 创建线程(传入属性)
    ret = pthread_create(&tid, &attr, thread_func, NULL);
    if (ret != 0) {
        printf("创建线程失败:%s\n", strerror(ret));
        return -1;
    }

    // 销毁属性(创建线程后即可销毁)
    pthread_attr_destroy(&attr);

    sleep(3); // 等待线程执行完毕
    printf("主线程退出\n");
    return 0;
}

五、总结

  • 线程等待用pthread_join,可获取退出状态并释放资源,适用于需同步结果的场景;
  • 线程分离用pthread_detach,自动回收资源,适用于无需关注结果的后台线程;
  • 线程属性可定制线程行为,核心是 "初始化→设置→使用→销毁" 的流程;
  • 新手需重点掌握 "joinable vs 分离" 的区别,避免僵尸线程或无效等待。

下一篇将讲解线程 ID 与进程地址空间布局,深入理解线程的内存分布,敬请关注!

下面补一份贯穿始终的代码

cpp 复制代码
#ifndef _THREAD_H_
#define _THREAD_H_

#include <iostream>
#include <cstdio>
#include <string>
#include <pthread.h>
#include <cstring>
#include <cstdint>
#include <functional>

static uint32_t number = 1;

template<class T>
class Thread
{
public:
    using func_t = std::function<void(T)>;
    Thread(func_t func, T date)
        : _tid(0)
        , _isdetach(false)
        , _isrunning(false)
        , res(nullptr)
        , _func(func)
        , _date(date)
    {
        _name = "pthread -> " + std::to_string(number++);
    }
    ~Thread()
    {
    }
    void EnableDetach() // 启动前设置分离
    {
        _isdetach = true;
    }
    void Detach() // 启动后设置分离
    {
        if (_isdetach || !_isrunning)
            return;
        pthread_detach(_tid);
        EnableDetach();
    }
    void EnableRunning()
    {
        _isrunning = true;
    }
    static void *Routine(void *args)
    {     
        ((Thread<T>*)args)->EnableRunning();
        if (((Thread<T>*)args)->_isdetach)
            pthread_detach(((Thread<T>*)args)->_tid);
        ((Thread<T>*)args)->_func(((Thread<T>*)args)->_date);
        return nullptr;
    }
    bool Start()
    {
        if (_isrunning)
            return false;
        int n = pthread_create(&_tid, nullptr, Routine, this);
        if (n != 0)
        {
            std::cout << "pthread_create fail : " << strerror(n) << std::endl;
            return false;
        }
        else 
        {
            std::cout << "pthread_create success, name : " << _name << std::endl;
            return true;
        }      
    }
    bool Stop() // cancel
    {
        if (_isrunning)
        {
            int n = pthread_cancel(_tid);
            if (n != 0)
            {
                std::cout << "stop thread error : " << strerror(n) << std::endl;
                return false;
            }
            else
            {
                std::cout << "stop thread success!" << std::endl;
                _isrunning = false;
                return true;
            }   
        }
        return false;
    }
    void Join()
    {
        if (_isdetach)
        {
            std::cout << "thread is detach!" << std::endl;
            return;
        }
        int n = pthread_join(_tid, &res);
        if (n != 0)
            std::cout << "join thread error : " << strerror(n) << std::endl;
        else
        {
            std::cout << "join thread success!" << std::endl;
            _isrunning = false;
        }
    }

private:
    pthread_t _tid;
    std::string _name;
    bool _isdetach;
    bool _isrunning;
    void *res;
    func_t _func;
    T _date;
};

#endif
cpp 复制代码
#include "Thread.hpp"
#include <unistd.h>

class game
{
public:
    void start()
    {
        std::cout << "请输入你的目标值 -> ";
        std::cin >> _target;
        _target %= 100;   
        int count = 1;     
        while (true)
        {
            int n1 = 0;
            int n2 = 0;           
            n1 = rand() % 100;
            n2 = rand() % 100;
            if (n1 + n2 == _target)
            {
                std::cout << "猜对啦!" << std::endl;
                break;
            }
            else 
            {
                printf("第 %d 次错误, 错误值为 : %d\n", count++, n1 + n2);
            }
            //sleep(1);
        }
    }
private:
    int _target;
};

void test(game g)
{
    g.start();
}

int main()
{
    game g;
    Thread<game> t(test, g);
    t.Start();
    t.Join();
    return 0;
}



/*
void func(int cnt)
{
    while (cnt--)
    {
        std::cout << "我是一个新线程!" << std::endl;
        sleep(1);
    }
}

int main()
{
    int cnt = 10;
    //Thread<int> thread(func, cnt);
    Thread<int> thread([](int cnt){
        while (cnt--)
        {
            std::cout << "我是一个新线程!" << std::endl;
            sleep(1);
        } 
    }, cnt);
    thread.Start();
    thread.Join();
    return 0;
}
*/
相关推荐
渐暖°2 小时前
【leetcode算法从入门到精通】9. 回文数
算法·leetcode·职场和发展
星火开发设计2 小时前
using 关键字:命名空间的使用与注意事项
开发语言·c++·学习·算法·编程·知识
ZPC82102 小时前
机器人手眼标定
人工智能·python·数码相机·算法·机器人
知我心·2 小时前
Java实现常见算法
算法
80530单词突击赢2 小时前
C++类与对象进阶核心技巧
算法
我是咸鱼不闲呀2 小时前
力扣Hot100系列18(Java)——[技巧]总结 (只出现一次的数字,多数元素,颜色分类,下一个排列,寻找重复数)
java·算法·leetcode
mit6.8242 小时前
贪心构造+枚举子集+有向图判环
算法
firstacui2 小时前
Docker compose的安装与使用
运维·docker·容器
孞㐑¥2 小时前
算法—字符串
开发语言·c++·经验分享·笔记·算法