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

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

  • 进程是操作系统资源分配的基本单位
  • 线程是 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也是这样的)。
  • 核心特点:线程库运行在用户态,负责线程的管理(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;
}

. Linux 线程底层原理

  • Linux 内核没有原生线程概念 ,仅有轻量级进程(LWP),通过clone()系统调用创建。
  • fork()创建子进程底层同样封装clone()
  • 用户态封装 POSIX 标准pthread线程库(NPTL),我们写的线程本质就是创建内核 LWP。
  • Linux 下用户态线程:内核 LWP = 1:1一对一映射。

2. pthread_create核心创建函数

  • 共 4 个参数:
    1. thread:输出参数,返回用户层线程 ID
    2. attr:线程属性,默认填NULL即可。
    3. start_routine:线程入口函数(函数指针),新线程执行的代码逻辑。
    4. arg:入口函数的入参,void*万能指针,可传递任意类型数据。
  • 错误处理特殊规则:成功返回0,失败直接返回错误码 ,不使用全局errno

3. 线程 ID pthread_t 的本质

  • 打印pthread_t得到的大数值,和终端ps -aL查到的内核 LWP 编号完全不一致
  • pthread_t不是内核 LWP 号,是pthread 线程库自行维护的值
  • 本质:转为十六进制后,是一个虚拟内存地址
  • 原理:指向线程库内存中、管理线程信息的结构体(线程控制块)的首地址。

4. 编译避坑细节

  • 旧版系统:pthread不属于默认 C 标准库,编译必须加 -lpthread 链接选项。
  • 新版系统(Ubuntu24.04、glibc2.34+):pthread 已合并进libc,不加-lpthread也可编译通过。
  • 开发建议:为保证跨平台兼容性,始终保留-lpthread编译选项

配套函数: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;
}

1. 获取线程 ID:pthread_self() 函数

  • 核心功能 :线程运行过程中,获取自身的用户层线程 ID(TID)。
  • 返回值pthread_t 类型,本质是无符号长整型、线程管理结构体的内存地址。
  • 关键区别
    • pthread_create():由父线程调用,用来拿到新建子线程的 ID
    • pthread_self():由当前正在运行的线程调用,获取自己本身的 ID

2. 线程创建核心逻辑

  • 线程入口:threadrun 函数,是新线程启动后执行的代码起点。
  • 参数传递:利用万能void*指针向新线程传入任意数据,在线程函数内再强制类型转换还原。
  • 并发特性:主线程main()与新建线程,各自独立循环运行、互不干扰,交替执行输出,实现真正并发。

3. PID 与 TID 核心区别(运行结论)

  1. PID(进程 ID)
    • 同一个进程内,所有线程的 PID 完全相同
    • 调用getpid()获取,代表所属的整个进程
  2. TID(线程 ID,pthread_t
    • 同一个进程内,每一个线程的 TID 都唯一不同
    • 作用:在进程内部,区分、标识不同的独立执行流
概念 对应函数 / 要素 核心作用
创建新线程 pthread_create 发起、启动一个全新子线程
获取自身线程 ID pthread_self 线程内部查询自己专属的pthread_t
获取进程标识 getpid 获取所属进程 ID,所有线程共用同一个
线程参数传递 void* 万能指针 跨线程传递任意复杂数据的通用载体

最终一句话总结

Linux 多线程,就是同一个进程 PID 之下,拥有多个独立、可并发运行的线程(TID);每个线程都可以单独识别自身身份,共享进程资源、同时独立调度执行。

关键说明:

  • 线程的执行顺序由内核调度决定,主线程和新线程谁先执行不固定;
  • 用户态的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;
}

1. ❌ 绝对禁止的危险操作:子线程调用 exit()

  • 错误行为:子线程函数内部执行 exit(数值)
  • 致命后果:exit() 的作用是销毁整个进程,只要任意一个子线程调用它,主线程 + 所有其他子线程都会立刻全部终止,进程整体崩溃。

2. ✅ 线程安全退出的 3 种正规方式

  1. 方式一:函数正常 return(最推荐)

    • 用法:线程执行完业务逻辑,直接 return 返回指针结果
    • 优点:自然结束生命周期,逻辑清晰,不会影响其他任何线程
  2. 方式二:pthread_exit() 主动终止(线程自结束)

    • 用法:子线程任意位置调用 pthread_exit(返回指针)
    • 特点:专门终止当前调用线程;深层嵌套函数中直接退出线程,比逐层 return 高效
  3. 方式三:pthread_cancel() 被动强制终止(他杀)

    • 用法:主线程 / 其他线程,通过目标线程 ID,调用该函数强制终结目标线程
    • 补充:不推荐线程自己调用 pthread_cancel(pthread_self()),存在延迟、风险高

3. 📌 被取消线程的返回值规则

  • 正常主动退出:线程 return (void*)100,主线程 join 可以拿到自定义返回值100
  • 被强制cancel取消:线程无法自定义返回值,系统强制设定退出码为 -1
  • 底层宏定义:PTHREAD_CANCELED ((void *) -1),主线程拿到该值,即可判定线程是被强制杀死而非正常结束

4. 💡 线程退出码核心思想

  • 线程退出三种场景:正常跑完、运行出错、异常终止
  • 自定义return数值:就是线程的退出码,用来向主线程上报自身任务执行结果
  • 引出后续知识点:pthread_join(),主线程等待线程、接收线程退出码的配套机制

一句话最终总结

正常结束线程优先用return,主动终止用pthread_exit(),强制关停用pthread_cancel()(固定返回 - 1);严禁子线程调用exit(),直接团灭整个进程

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;
};
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;
}

使用方式

完整实战代码:线程分离

  • 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())分离自身。

三. 补充: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 调度的唯一标识。
相关推荐
ss2732 小时前
食谱推荐系统功能测试如何写?
java·数据库·spring boot·功能测试
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第15题:JDK1.7中HashMap扩容为什么会发生死循环?如何解决
java·开发语言·数据结构·后端·面试·哈希算法
try2find2 小时前
打印ascii码报错问题
java·linux·前端
014-code2 小时前
CompletableFuture 实战模板(超时、组合、异常链处理)
java·数据库
Nicander2 小时前
多数据源下@transcation事务踩坑
java·后端
郑州光合科技余经理3 小时前
同城O2O海外版二次开发实战:从支付网关到配送算法
开发语言·前端·后端·算法·架构·uni-app·php
それども3 小时前
DELETE 和 TRUNCATE TABLE区别
java·数据库·mysql
南子北游4 小时前
Python学习(基础语法1)
开发语言·python·学习
张健11564096484 小时前
使用信号量限制并发数量
开发语言·c++