【Linux 线程进阶】进程 vs 线程资源划分 + 线程控制全详解


🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!


🎬 博主简介:


文章目录

  • 前言:
  • [一. 进程 VS 线程:资源共享与私有划分](#一. 进程 VS 线程:资源共享与私有划分)
    • [1.1 核心定位再梳理(整体图示解析)](#1.1 核心定位再梳理(整体图示解析))
    • [1.2 线程间共享的进程资源](#1.2 线程间共享的进程资源)
    • [1.3 线程私有的独立资源](#1.3 线程私有的独立资源)
    • [1.4 关键问题:为什么一个线程崩溃,整个进程都会退出?](#1.4 关键问题:为什么一个线程崩溃,整个进程都会退出?)
  • [二. Linux 线程控制:从创建到回收全流程](#二. Linux 线程控制:从创建到回收全流程)
    • [2.1 前置说明:pthread 线程库](#2.1 前置说明:pthread 线程库)
    • [2.2 线程创建:pthread_create](#2.2 线程创建:pthread_create)
    • [2.3 多线程创建 -- 构建任务(等到线程等待结束还有一个优化)](#2.3 多线程创建 -- 构建任务(等到线程等待结束还有一个优化))
    • [2.4 线程终止:3 种合法终止方式](#2.4 线程终止:3 种合法终止方式)
      • [2.4.1 方式 1:线程入口函数 return 返回](#2.4.1 方式 1:线程入口函数 return 返回)
      • [2.4.2 方式 2:pthread_exit:线程主动终止自身](#2.4.2 方式 2:pthread_exit:线程主动终止自身)
      • [2.4.3 方式 3:pthread_cancel:终止同进程的其他线程](#2.4.3 方式 3:pthread_cancel:终止同进程的其他线程)
    • [2.5 线程等待:pthread_join(附加:指针和指针变量辨析)](#2.5 线程等待:pthread_join(附加:指针和指针变量辨析))
    • [2.6 多线程的优化写法(加上线程等待)](#2.6 多线程的优化写法(加上线程等待))
    • [2.7 线程分离:pthread_detach](#2.7 线程分离:pthread_detach)
  • [三. 补充:LWP 与原生线程库的封装关系](#三. 补充:LWP 与原生线程库的封装关系)
  • 结尾:

前言:

大家好,我是深耕 Linux 内核与开发的学习者,上一篇我们吃透了线程的本质概念与内核实现,本篇覆盖**「进程 VS 线程资源划分」+「Linux 线程控制」**,资源共享 / 独占的底层逻辑,到线程创建、终止、等待、分离的全流程实战,知识点无遗漏、代码可直接运行,兼顾学习理解与面试复习。

上一篇我们明确了核心定义:

  • 进程是操作系统资源分配的基本单位
  • 线程是 CPU 调度的最小单位,是进程内部的执行分支,Linux 下本质是轻量级进程(LWP)

而想要真正用好线程,必须先搞懂两个核心问题:

  1. 同进程内的线程,哪些资源共享?哪些资源私有?
  2. 如何通过系统接口,完成线程的全生命周期控制?

本篇就围绕这两个问题,把知识点讲透、代码写全。


一. 进程 VS 线程:资源共享与私有划分

线程的 "轻量化",本质是 共享了进程的绝大部分资源,仅私有运行必需的最小上下文。我们先把共享与私有资源划清,这是理解线程所有特性的基础。

1.1 核心定位再梳理(整体图示解析)

  • 进程 = 内核数据结构(task_struct/mm_struct 等) + 私有的代码和数据,承担系统资源分配
  • 线程 = 进程内部的执行分支,粒度更细、更轻,仅需维护自身运行上下文
  • 内核通过task_struct统一管理进程和线程,线程与进程的核心差异,就在于资源是否共享。


1.2 线程间共享的进程资源

同进程内的所有线程,共享进程地址空间内的绝大部分资源,无需额外申请,这也是线程创建 / 切换成本极低的核心原因:

  • 地址空间核心段:代码段(Text Segment)、数据段(Data Segment)、堆区、共享区
    • 全局变量、静态变量、堆内申请的内存,所有线程都可直接访问修改
  • 文件系统相关:文件描述符表、当前工作目录、用户 id / 组 id
    • 一个线程打开的文件,其他线程可直接读写
  • 信号相关:每种信号的处理方式(SIG_IGN/SIG_DFL/ 自定义处理函数)
    • 信号是发给进程的,任意线程收到信号,都会触发整个进程的信号处理函数
  • 进程内核数据:页表、mm_struct 内存描述符、vm_area_struct 内存区域结构

1.3 线程私有的独立资源

线程要独立被 CPU 调度执行,必须私有运行时的上下文数据,这些资源线程间完全隔离,互不影响:

  • 线程 ID(TID):用户态 pthread 库的唯一标识,内核态对应 LWP 号
  • 寄存器上下文:CPU 寄存器的值,线程切换时保存 / 恢复,保证执行流不混乱
  • 独立栈空间:每个线程有自己的私有栈,存放局部变量、函数调用栈帧,互不干扰
  • errno 变量:系统调用错误码,线程私有,避免多线程间错误码互相覆盖
  • 信号屏蔽字:每个线程可独立设置信号屏蔽规则,不影响其他线程
  • 调度优先级:线程可独立设置调度优先级,由内核单独调度
  • 线程局部存储(TLS):线程私有的全局变量,仅当前线程可访问

1.4 关键问题:为什么一个线程崩溃,整个进程都会退出?

这是面试高频考点,结合资源划分就能瞬间理解:

  • 线程触发异常(除零、野指针、非法内存访问),内核会向进程发送致命信号,而非单独发给线程;
  • 信号的处理方式是进程级共享的,进程收到致命信号后,会直接终止整个进程;
  • 进程终止后,地址空间、文件描述符等所有资源都会被回收,所有线程自然随之退出。
  • 上篇博客中提到过,更详细的解析可以去上篇博客中看看

二. Linux 线程控制:从创建到回收全流程

Linux 内核仅提供轻量级进程的创建能力,我们日常使用的线程接口,都来自POSIX 标准的 pthread 线程库 ,所有接口都以pthread_开头,编译时必须通过-lpthread链接线程库。

2.1 前置说明:pthread 线程库

  • 头文件:必须包含<pthread.h>
  • 编译命令:gcc xxx.c -o xxx -lpthread
  • 核心特点:线程库运行在用户态,负责线程的管理(TCB 线程控制块、线程栈分配),底层通过clone系统调用让内核创建 LWP(其实我们之前学习的fork也是这样的)。
  • 代码演示:
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <sys/wait.h>
#include <signal.h>

// 子线程函数(LWP执行的函数)
int thread_func(void *arg)
{
    int thread_id = *(int *)arg;
    int count = 0;

    printf("LWP %d (PID: %d, 父进程: %d) 开始执行\n",
           thread_id, getpid(), getppid());

    for (int i = 1; i <= 5; i++)
    {
        printf("LWP %d 计数: %d\n", thread_id, i);
        sleep(1);
    }

    printf("LWP %d 执行完毕\n", thread_id);
    return thread_id * 10; // 返回退出码
}

int main()
{
    const int NUM_THREADS = 5;
    void **stacks;                // 栈空间指针数组
    int *thread_ids;              // 线程ID数组
    pid_t *pids;                  // LWP的PID数组
    int stack_size = 1024 * 1024; // 每个栈1MB

    // 分配内存
    stacks = (void **)malloc(NUM_THREADS * sizeof(void *));
    thread_ids = (int *)malloc(NUM_THREADS * sizeof(int));
    pids = (pid_t *)malloc(NUM_THREADS * sizeof(pid_t));

    if (!stacks || !thread_ids || !pids)
    {
        perror("malloc失败");
        exit(1);
    }

    printf("父进程 PID: %d\n\n", getpid());

    // 创建5个LWP
    for (int i = 0; i < NUM_THREADS; i++)
    {
        // 为每个LWP分配独立的栈空间(向下增长,分配在堆上)
        stacks[i] = malloc(stack_size);
        if (!stacks[i])
        {
            perror("栈分配失败");
            exit(1);
        }

        thread_ids[i] = i + 1;

        // 使用clone创建LWP
        // CLONE_VM:      共享内存空间(父子进程共享地址空间)
        // CLONE_FS:      共享文件系统信息
        // CLONE_FILES:   共享文件描述符表
        // CLONE_SIGHAND: 共享信号处理器
        // CLONE_THREAD:  放入同一线程组(与父进程共享PID)
        // SIGCHLD:       子进程退出时发送信号
        pids[i] = clone(thread_func,
                        stacks[i] + stack_size, // 栈顶(栈向下增长)
                        CLONE_VM | CLONE_FS | CLONE_FILES |
                            CLONE_SIGHAND | CLONE_THREAD | SIGCHLD,
                        &thread_ids[i]);

        if (pids[i] == -1)
        {
            perror("clone失败");
            exit(1);
        }

        printf("创建 LWP %d, LWP ID: %d\n", thread_ids[i], pids[i]);
    }

    printf("\n所有LWP已创建, 等待执行完成...\n");
    printf("注意:由于共享内存,主进程需要等待一段时间\n\n");

    // 注意:因为使用了CLONE_THREAD,不能使用waitpid等待
    // 简单的做法是等待足够长时间让子线程完成
    sleep(8);

    printf("\n主进程等待结束, 清理资源\n");

    // 清理栈空间
    for (int i = 0; i < NUM_THREADS; i++)
    {
        free(stacks[i]);
    }
    free(stacks);
    free(thread_ids);
    free(pids);

    return 0;
}


2.2 线程创建:pthread_create

用于创建一个新的用户态线程,是线程控制最基础的接口。

函数原型:

cpp 复制代码
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);

参数详解:

参数 作用说明
thread 输出型参数,用于返回新创建线程的用户态线程 ID
attr 线程属性设置,传 NULL 表示使用默认属性(栈大小、调度策略等)
start_routine 函数指针,线程的入口执行函数,线程创建成功后,会从这个函数开始执行
arg 传递给线程入口函数的参数

返回值:

  • 成功:返回 0

  • 失败:直接返回错误码(不会设置 errno),无需通过 perror 打印,用strerror(ret)解析错误信息

  • 图示中的测试代码

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

// int g_val = 100;
// int *p = nullptr;

// void hello(const std::string &name) {
//     printf("haha, I am common function!, %s\n", name.c_str());
//     sleep(5);
// }

class Task
{
public:
    Task() = default;
    void operator()()
    {

    }
    ~Task() = default;
private:
};

void *threaddrun1(void *args)
{
    // p = (int*)malloc(sizeof(int) * 10);
    std::string threadname = static_cast<const char*>(args);
    while(true)
    {
        sleep(1);
        std::cout << threadname << std::endl;
        // printf("%s is running, g_val: %d, &g_val: %p\n", threadname.c_str(), g_val, &g_val);
        // sleep(1);
        // hello(threadname);
    }
}

void* threaddrun2(void *args)
{
    std::string threadname = static_cast<const char*>(args);
    while(true)
    {
        sleep(1);
        std::cout << threadname << std::endl;
        // printf("%s is running, g_val: %d, &g_val: %p\n", threadname.c_str(), g_val, &g_val);
        // sleep(1);
        // g_val++;
        // hello(threadname);
    }
}

int main()
{
    pthread_t t1, t2;
    // 这里也可以定义类 Task t; -- 然后传进去
    pthread_create(&t1, nullptr, threaddrun1, (void*)"thread-1");
    pthread_create(&t2, nullptr, threaddrun2, (void*)"thread-2");
    // pthread_join(t1, nullptr);
    // pthread_join(t2, nullptr);
    while(true)
    {
        // printf("Main thread, thread1 id: %ld, thread2 id: %ld\n", t1, t2);
        printf("Main thread, thread1 id: %p, thread2 id: %p\n", t1, t2);
        sleep(1);
    }
    return 0;
}



配套函数:pthread_self

cpp 复制代码
pthread_t pthread_self(void);

功能:获取当前线程的用户态线程 ID,和进程的getpid()作用一致。

完整实战代码:创建线程

cpp 复制代码
// 测试线程创建
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <string>
void* threadrun(void* arg)
{
    std::string name = static_cast<const char*>(arg);
    while(true)
    {
        printf("我是一个新线程, tid: %lu, pid: %d\n", pthread_self(), getpid());
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, NULL, threadrun, (void*)"thread-1");

    while(true)
    {
        printf("创建新进程成功, new tid: %lu, main tid: %lu, pid: %d\n", tid, pthread_self(), getpid());
        sleep(1);
    }
    return 0;
}

关键说明:

  • 线程的执行顺序由内核调度决定,主线程和新线程谁先执行不固定;
  • 用户态的pthread_t线程 ID,本质是进程地址空间中线程 TCB 结构体的地址,和内核 LWP 号不是一个概念;
  • 主线程如果提前退出(调用 exit/return),整个进程会终止,所有线程都会被强制退出。

2.3 多线程创建 -- 构建任务(等到线程等待结束还有一个优化)

  • Task.hpp
cpp 复制代码
#pragma once

#include <iostream>
#include <string>

class Task
{
public:
    Task(const std::string &who, int x, int y):_x(x), _y(y), _who(who)
    {}
    Task()
    {}
    void operator()()
    {
        std::cout << _who << " execute task: " <<_x << " + " << _y << " = " << _x + _y << std::endl;
    }
    ~Task()
    {}
private:
    int _x;
    int _y;
    std::string _who;
};
  • testThread.cc
cpp 复制代码
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <string>
#include <vector>

int g_size = 64;
void* threadrun(void* arg)
{
    std::string name = static_cast<const char*>(arg);
    delete[] (char*)arg;
    while(true)
    {
        printf("我是一个新线程, tid: %lu, pid: %d, name: %s\n", pthread_self(), getpid(), name.c_str());  
        sleep(1);
    }
    return nullptr;
}

// ./CreateThread threadnum;
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        printf("Usage: %s threadnum\n", argv[0]);
        return -1;
    }
    int threadnum = std::stoi(argv[1]);
    std::vector<pthread_t> tids;
    for(int i = 0; i < threadnum; i++)
    {
        pthread_t tid;
        // char threadname[g_size]; // 这个是不太行的, 如果要让每个线程都有自己的名称, 则需要动态分配内存, 不能使用栈内存
        char* threadname = (char*)malloc(g_size);
        snprintf(threadname, g_size, "thread-%d", i + 1);
        pthread_create(&tid, NULL, threadrun, (void*)threadname);
        // sleep(1); 这个是不可以完全解决这个问题的
        tids.push_back(tid);
    }

    sleep(10);
    for(auto& tid : tids)
    {
        printf("main for 创建新进程成功, new tid: %lu, main tid: %lu, pid: %d\n", tid, pthread_self(), getpid());
    }

    // 主线程
    while(true)
    {
        std::cout << "main thread is running..." << std::endl;
        sleep(1);
    }

    return 0;
}


任务构建版

cpp 复制代码
// 构建一个任务进行测试
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <string>
#include <vector>
#include "Task.hpp"

int g_size = 64;
void* threadrun(void* arg)
{
    Task *t = static_cast<Task*>(arg);
    sleep(1);
    (*t)();
    sleep(1);

    while(true)
    {
        sleep(1);
        // printf("我是一个新线程, tid: %lu, pid: %d, name: %s\n", pthread_self(), getpid(), name.c_str());  
        // sleep(1);
    }
    return nullptr;
}

// ./CreateThread threadnum;
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        printf("Usage: %s threadnum\n", argv[0]);
        return -1;
    }
    int threadnum = std::stoi(argv[1]);
    std::vector<pthread_t> tids;
    for(int i = 0; i < threadnum; i++)
    {
        pthread_t tid;
        char threadname[g_size]; // 这个是不太行的, 如果要让每个线程都有自己的名称, 则需要动态分配内存, 不能使用栈内存
        // char* threadname = (char*)malloc(g_size);
        snprintf(threadname, g_size, "thread-%d", i + 1);

        Task *t = new Task(threadname, 10 + i, 20 * i);
        pthread_create(&tid, NULL, threadrun, (void*)t);
        tids.push_back(tid);
        sleep(1);
    }

    sleep(10);
    for(auto& tid : tids)
    {
        printf("main for 创建新进程成功, new tid: %lu, main tid: %lu, pid: %d\n", tid, pthread_self(), getpid());
    }

    // 主线程
    while(true)
    {
        std::cout << "main thread is running..." << std::endl;
        sleep(1);
    }

    return 0;
}


2.4 线程终止:3 种合法终止方式

如果需要只终止某个线程,而不终止整个进程,有 3 种安全合法的方式,严禁在线程内调用 exit (),会导致整个进程退出。

2.4.1 方式 1:线程入口函数 return 返回

最推荐的方式,线程入口函数执行完毕 return,线程自动终止,返回值可被pthread_join获取。

cpp 复制代码
// 测试线程退出
// 1. return 退出线程函数
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <string>

const int g_size = 64;
void* threadrun(void* arg)
{
    std::string name = static_cast<const char*>(arg);
    int cnt = 5;
    while(cnt)
    {
        printf("我是一个新进程: tid: 0x%lx, pid: %d, name: %s, cnt: %d\n", 
                pthread_self(), getpid(), name.c_str(), cnt);
        cnt--;
        sleep(1);
        return nullptr;
    }
    // 新进程退出
    // 只要自己的线程函数跑完,线程自然退出了 -- 调用return,表示线程退出
    // return nullptr;
}

int main()
{
    pthread_t tid;
    char threadname[g_size]; 
    snprintf(threadname, g_size, "thread-%d", 1);
    pthread_create(&tid, NULL, threadrun, (void*)threadname);

    while(true)
        pause();

    
    return 0;
}

2.4.2 方式 2:pthread_exit:线程主动终止自身

线程内调用该函数,主动终止自身,效果和 return 一致,返回值可被pthread_join获取。

函数原型

cpp 复制代码
void pthread_exit(void *value_ptr);
  • 参数value_ptr:线程退出的返回值,不能指向线程栈内的局部变量;
  • 无返回值,调用后线程直接终止,不会再返回。

代码示例:

cpp 复制代码
// 2. pthread_exit() 退出线程函数
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <string>

const int g_size = 64;
void* threadrun(void* arg)
{
    std::string name = static_cast<const char*>(arg);
    int cnt = 5;
    while(cnt)
    {
        printf("我是一个新进程: tid: 0x%lx, pid: %d, name: %s, cnt: %d\n", 
                pthread_self(), getpid(), name.c_str(), cnt);
        cnt--;
        sleep(1);
        // pthread_exit(nullptr);
    }
    // 新进程退出
    pthread_exit(nullptr);
    return nullptr;
}

int main()
{
    pthread_t tid;
    char threadname[g_size]; 
    snprintf(threadname, g_size, "thread-%d", 1);
    pthread_create(&tid, NULL, threadrun, (void*)threadname);

    // pthread_exit(nullptr);
    while(true)
        pause();

    
    return 0;
}

2.4.3 方式 3:pthread_cancel:终止同进程的其他线程

一个线程调用该函数,取消同一个进程内的另一个线程,被取消的线程默认退出码是PTHREAD_CANCELED(值为 - 1)。

函数原型

cpp 复制代码
int pthread_cancel(pthread_t thread);
  • 参数thread:要终止的目标线程 ID;
  • 返回值:成功返回 0,失败返回错误码。

代码示例

cpp 复制代码
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <string>

const int g_size = 64;
void* threadrun(void* arg)
{
    std::string name = static_cast<const char*>(arg);
    int cnt = 5;
    while(cnt)
    {
        printf("我是一个新进程: tid: 0x%lx, pid: %d, name: %s, cnt: %d\n", 
                pthread_self(), getpid(), name.c_str(), cnt);
        cnt--;
        sleep(1);
        // pthread_cancel(pthread_self()); // 自己取消自己
    }
    return (void*)10;
}

int main()
{
    pthread_t tid;
    char threadname[g_size]; 
    snprintf(threadname, g_size, "thread-%d", 1);
    pthread_create(&tid, NULL, threadrun, (void*)threadname);

    int n = pthread_cancel(tid);
    if(n != 0)
    {
        printf("pthread_cancel error: %d\n", n);
    }
    else
    {
        printf("pthread_cancel success\n");
    }
    
    void* ret = nullptr;
    pthread_join(tid, &ret);
    printf("join 0x%lx success, ret code: %lld\n", tid, (long long)ret);
    while(true)
        pause();

    
    return 0;
}


2.5 线程等待:pthread_join(附加:指针和指针变量辨析)

线程退出后,其资源不会被自动释放,必须通过pthread_join等待线程退出,回收资源,否则会造成内存泄漏。

函数原型

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

参数详解

  • thread:要等待的目标线程 ID;
  • value_ptr:输出型参数,用于接收线程的退出返回值。如果不关心退出码,传 NULL 即可。

返回值

  • 成功:返回 0
  • 失败:返回错误码

线程退出码的 3 种情况

  1. 线程通过return返回:value_ptr指向的是线程 return 的返回值;
  2. 线程通过pthread_exit终止:value_ptr指向的是pthread_exit传入的参数;
  3. 线程被pthread_cancel取消:value_ptr指向的是常数PTHREAD_CANCELED

完整实战代码:线程等待全场景

cpp 复制代码
// 测试线程等待
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <string>

const int g_size = 64;
void* threadrun(void* arg)
{
    std::string name = static_cast<const char*>(arg);
    int cnt = 5;
    while(cnt)
    {
        printf("我是一个新进程: tid: 0x%lx, pid: %d, name: %s, cnt: %d\n", 
                pthread_self(), getpid(), name.c_str(), cnt);
        cnt--;
        sleep(1);
    }
    // return (void*)10;
    pthread_exit((void*)100); // 再试试这个
}

int main()
{
    pthread_t tid;
    char threadname[g_size]; 
    snprintf(threadname, g_size, "thread-%d", 1);
    pthread_create(&tid, NULL, threadrun, (void*)threadname);
    
    void* ret = nullptr;
    pthread_join(tid, &ret);
    printf("join 0x%lx success, ret code: %lld\n", tid, (long long)ret);
    while(true)
        pause();

    
    return 0;
}




2.6 多线程的优化写法(加上线程等待)

  • Task.hpp
cpp 复制代码
#pragma once

#include <iostream>
#include <string>

class Task
{
public:
    Task(const std::string &who, int x, int y):_x(x), _y(y), _who(who)
    {}
    Task()
    {}
    void Execute()
    {
        _result = _x + _y;
    }
    std::string GetResult()
    {
        return std::to_string(_x) + " + " + std::to_string(_y) + " = " + std::to_string(_result);
    }
    ~Task()
    {}
private:
    int _x;
    int _y;
    int _result;
    std::string _who;
};
  • testThread.cc
cpp 复制代码
// 多线程的优化
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <string>
#include <vector>
#include "Task.hpp"

int g_size = 64;
void* threadrun(void* arg)
{
    Task *t = static_cast<Task*>(arg);
    t->Execute();
    return t;
}

// ./CreateThread threadnum;
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        printf("Usage: %s threadnum\n", argv[0]);
        return -1;
    }
    int threadnum = std::stoi(argv[1]);
    std::vector<pthread_t> tids;
    for(int i = 0; i < threadnum; i++)
    {
        pthread_t tid;
        char threadname[g_size]; // 这个是不太行的, 如果要让每个线程都有自己的名称, 则需要动态分配内存, 不能使用栈内存
        // char* threadname = (char*)malloc(g_size);
        snprintf(threadname, g_size, "thread-%d", i + 1);

        Task *t = new Task(threadname, 10 + i, 20 * i);
        pthread_create(&tid, NULL, threadrun, (void*)t);
        tids.push_back(tid);
        std::cout << "create thread " << threadname << " success, tid: " << tid << std::endl;
        // sleep(1);
    }


    // 等待所有线程执行完成
    std::vector<Task*> result_list;
    for(auto& tid : tids)
    {
        Task *t = nullptr;
        pthread_join(tid, (void**)&t);
        result_list.push_back(t);
        std::cout << "join success: " << tid << std::endl;
    }

    // 处理结果清单
    std::cout << "result list: " << std::endl;
    for(auto& t : result_list)
    {
        std::cout << t->GetResult() << std::endl;
    }

    return 0;
}



2.7 线程分离:pthread_detach

默认情况下,线程是joinable状态,退出后必须通过pthread_join回收资源。如果我们不关心线程的返回值,不想阻塞等待线程退出,可以将线程设置为分离状态,线程退出后,系统会自动回收其资源,无需手动 join。

函数原型

cpp 复制代码
int pthread_detach(pthread_t thread);
  • 参数thread:要分离的目标线程 ID;
  • 返回值:成功返回 0,失败返回错误码。

使用方式

  • 其他线程分离:主线程创建子线程后,调用pthread_detach(tid)分离子线程;
  • 线程自分离:子线程内调用pthread_detach(pthread_self())分离自身。

完整实战代码:线程分离

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

// 线程自分离示例
void* thread_detach_self(void* arg) {
    // 线程自分离
    pthread_detach(pthread_self());
    printf("子线程:已完成自分离,运行3秒后退出\n");
    sleep(3);
    printf("子线程:退出,系统自动回收资源\n");
    pthread_exit(NULL);
}

int main() {
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, thread_detach_self, NULL);
    if (ret != 0) {
        fprintf(stderr, "创建线程失败:%s\n", strerror(ret));
        exit(1);
    }
    printf("主线程:子线程已创建,无需join等待\n");

    // 尝试join分离的线程,会直接失败
    ret = pthread_join(tid, NULL);
    if (ret != 0) {
        fprintf(stderr, "join分离线程失败:%s\n", strerror(ret));
    }

    // 主线程等待子线程退出,否则进程退出会强制结束线程
    sleep(4);
    printf("主线程退出\n");
    return 0;
}




三. 补充:LWP 与原生线程库的封装关系

很多同学会疑惑:用户态的 pthread 线程,和内核态的 LWP 是什么关系?TCB 又是什么?这里先进行一个简单的讲解,我们后面的博客中还会详细说的

  • 内核层:轻量级进程 LWP

    • Linux 内核没有线程的概念,只认task_struct,我们创建的每一个用户态线程,内核都会对应创建一个 LWP(轻量级进程),拥有独立的task_struct,是 CPU 调度的最小单位。
  • 用户层:pthread

  • 线程库线程库是运行在进程地址空间共享区的动态库,负责用户态线程的管理:

    • 为每个线程分配独立栈空间、创建 TCB(线程控制块);
    • 维护线程 ID、线程属性、退出码等信息;
    • 底层通过clone系统调用,让内核创建 LWP,建立用户态线程和内核 LWP 的 1:1 对应关系。
  • 用户态线程 ID vs 内核 LWP 号

    • pthread_t:用户态线程 ID,本质是进程地址空间中 TCB 结构体的地址,仅在进程内唯一;
    • LWP 号:内核态轻量级进程 ID,系统全局唯一,通过ps -aL可查看,是 CPU 调度的唯一标识。

结尾:

html 复制代码
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
  • 思维导图

结语:本篇我们完整梳理了进程与线程的资源划分,以及线程创建、终止、等待、分离的全生命周期控制,这是 Linux 多线程开发的核心基础。理解了资源共享与私有,就能避开多线程开发的大部分坑;掌握了线程控制接口,就能完成基础的多线程程序开发。下一篇我们会继续深入线程安全、同步互斥、互斥锁、条件变量等进阶内容,欢迎点赞收藏,一起交流学习~

✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど

相关推荐
枫叶落雨2222 小时前
ClassPathXmlApplicationContext
java·开发语言
唐樽2 小时前
C++ 竞赛学习路线笔记
c++·笔记·学习
ShineWinsu2 小时前
对于Linux:文件操作以及文件IO的解析
linux·c++·面试·笔试·io·shell·文件操作
ZKNOW甄知科技2 小时前
数智同行:甄知科技2026年Q1季度回顾
运维·服务器·人工智能·科技·程序人生·安全·自动化
-SGlow-2 小时前
Linux相关概念和易错知识点(52)(基于System V的信号量和消息队列)
linux·运维·服务器
gelald2 小时前
Spring Boot - 自动配置原理
java·spring boot·后端
jikemaoshiyanshi2 小时前
B2B企业GEO服务商哪家好?深度解析径硕科技(JINGdigital)及其JINGEO产品为何是首选
大数据·运维·人工智能·科技
江畔何人初2 小时前
TCP的三次握手与四次挥手
linux·服务器·网络·网络协议·tcp/ip
hssfscv2 小时前
软件设计师下午题六——Java的各种设计模式
java·算法·设计模式