线程进阶实战:资源划分与线程控制核心指南

🔥个人主页:Cx330🌸

❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》

《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔

《Git深度解析》:版本管理实战全解

🌟心向往之行必能至


🎥Cx330🌸的简介:


目录

前言:

一、前置认知:为什么需要资源划分与线程控制?

[二、 进程 VS 线程:资源共享与私有划分](#二、 进程 VS 线程:资源共享与私有划分)

[1.1 线程间共享的进程资源](#1.1 线程间共享的进程资源)

[1.2 线程私有的独立资源](#1.2 线程私有的独立资源)

[1.3 关键问题:为什么一个线程崩溃,整个进程都会退出?](#1.3 关键问题:为什么一个线程崩溃,整个进程都会退出?)

三、线程控制------精准管控,实现高效协作

[3.1 pthread 线程库](#3.1 pthread 线程库)

[3.2 线程创建:pthread_create](#3.2 线程创建:pthread_create)

[3.3 多线程创建 -- 构建任务](#3.3 多线程创建 – 构建任务)

[3.4 线程终止:3 种合法终止方式](#3.4 线程终止:3 种合法终止方式)

[3.4.1 方式 1:线程入口函数 return 返回](#3.4.1 方式 1:线程入口函数 return 返回)

[3.4.2 方式 2:pthread_exit:线程主动终止自身](#3.4.2 方式 2:pthread_exit:线程主动终止自身)

[3.4.3 方式 3:pthread_cancel:终止同进程的其他线程](#3.4.3 方式 3:pthread_cancel:终止同进程的其他线程)

[3.5 线程等待:pthread_join](#3.5 线程等待:pthread_join)

[3.6 多线程的优化写法------线程等待](#3.6 多线程的优化写法——线程等待)

[3.7 线程分离:pthread_detach](#3.7 线程分离:pthread_detach)

四、总结:线程进阶的核心逻辑


前言:

在多线程开发中,入门级的线程创建与启动仅仅是起点。当业务场景变得复杂,并发量提升、资源竞争加剧时,如何合理划分资源、精准控制线程行为,直接决定了程序的性能、稳定性与可维护性。本文聚焦线程进阶的两大核心模块------资源划分线程控制,结合底层原理与实战场景,帮你跳出"只会用线程,不会管线程"的困境,真正实现多线程的高效运用。


一、前置认知:为什么需要资源划分与线程控制?

操作系统中,进程是资源分配的基本单位,而线程是CPU调度的基本单位,线程依赖进程存在并共享其大部分资源。在未进行合理资源划分与线程控制的场景中,极易出现两大问题:

  • 资源竞争混乱:多个线程争抢同一资源(如内存、文件描述符),引发竞态条件、死锁等问题,导致程序运行异常或性能瓶颈;

  • 线程行为失控:线程创建/销毁频繁、执行顺序不可控、空闲线程占用资源,不仅会增加CPU上下文切换开销,还可能导致系统资源耗尽,甚至JVM崩溃。


简单来说,资源划分是"合理分配蛋糕",让每个线程都能高效获取所需资源;线程控制是"规范吃蛋糕的秩序",让线程按预期执行、协作,避免混乱。二者相辅相成,是多线程进阶的核心必修课。

二、 进程 VS 线程:资源共享与私有划分

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

1.1 线程间共享的进程资源

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

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

1.2 线程私有的独立资源

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

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

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

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

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

三、线程控制------精准管控,实现高效协作

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

3.1 pthread 线程库

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

代码演示:

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

void *threadrun(void *args)
{
    std::string name=static_cast<const char *>(args);
    while(true)
    {
        printf("我是一个新线程:tid: %lu,pid: %d\n",pthread_self(),getpid());
        sleep(1);
    }
}

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

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

3.2 线程创建:pthread_create

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

函数原型:

复制代码
#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)**解析错误信息

配套函数:pthread_self

复制代码
pthread_t pthread_self(void);

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

完整实战代码:创建线程

复制代码
// ./createThread num
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << argv[0] << " num" << std::endl;
        return 1;
    }

    int num = std::stoi(argv[1]); // 将字符串转化为数字
    std::vector<pthread_t> tids;
    for (int i = 0; i < num; i++)
    {
        // 如果我们要创建多线程呢?
        pthread_t tid;
        pthread_create(&tid, nullptr, threadrun, (void *)"thread-1");
        tids.push_back(tid);
    }
    sleep(1);

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

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

关键说明:

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

3.3 多线程创建 -- 构建任务

Task.hpp

复制代码
#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

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

    }

任务构建版

复制代码
// 构建一个任务进行测试
#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;
}

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

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

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

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

复制代码
// 测试线程退出
// 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;
}
3.4.2 方式 2:pthread_exit:线程主动终止自身

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

函数原型

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

代码示例:

复制代码
void *threadrun(void *args)
{
    std::string name=static_cast<const char *>(args);
    int cnt=5;
    while (cnt)
    {
        sleep(1);
        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;
    pthread_exit(nullptr);
    // exit(3); // 不能用来终止线程,它是用来终止进程的!多线程中,任意一个线程调用exit,都表示整个进程退出
}
3.4.3 方式 3:pthread_cancel:终止同进程的其他线程

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

函数原型

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

代码示例

复制代码
#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;
}

3.5 线程等待:pthread_join

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

函数原型

复制代码
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

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

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

int main()
{
    pthread_t tid;
    char threadname[gsize];
    snprintf(threadname,gsize,"thread-%d",1);

    pthread_create(&tid, nullptr, threadrun, (void*)threadname);

    void *ret=nullptr;
    pthread_join(tid,&ret);

    printf("join %lx success,ret code: %lld\n",tid,(long long)ret);

    return 0;
}

3.6 多线程的优化写法------线程等待

Task.hpp

复制代码
#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

复制代码
// 多线程的优化
#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;
}

3.7 线程分离:pthread_detach

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

函数原型

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

使用方式

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

完整实战代码:线程分离

复制代码
#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;
}

四、总结:线程进阶的核心逻辑

线程进阶的本质,是从"使用线程"到"管理线程"的转变。资源划分的核心是"隔离"------通过业务、资源、任务类型的拆分,减少竞争、提升资源利用率;线程控制的核心是"协作"------通过生命周期管理、顺序控制、中断处理,确保线程按预期执行,避免失控。

实际开发中,没有绝对最优的资源划分和线程控制方案,需结合业务场景、并发量、硬件资源灵活调整。记住:好的多线程设计,不是"线程越多越好",而是"资源分配合理、线程管控精准",既能发挥并发优势,又能保证系统稳定。

相关推荐
阿飞不想努力2 小时前
文件上传原理与实操
java·spring boot·vue·文件上传
SPC的存折2 小时前
分布式(加一键部署脚本)LNMP-Redis-Discuz5.0部署指南-小白详细版
linux·运维·服务器·数据库·redis·分布式·缓存
人道领域2 小时前
【黑马点评日记02】:Session+ThreadLocal实现短信登录
java·开发语言·spring·tomcat·intellij-idea
xiaoduo AI2 小时前
客服机器人知识库多久更新一次?智能 Agent 自动爬取新问答,过期话术能否及时淘汰?
大数据·人工智能·机器人
广州灵眸科技有限公司2 小时前
瑞芯微(EASY EAI)RV1126B 系统操作-线进程操作
开发语言·科技·嵌入式硬件·物联网
铅笔小新z2 小时前
【Linux】进程控制(上)
linux·运维·服务器
狂奔蜗牛飙车2 小时前
精准分工:云南省中职大数据赛项(3 人团队)
大数据·中职组大数据应用与服务赛项·大数据应用与服务·竞赛指南·大数据入门指南
AI周红伟2 小时前
Hermes Agent 工具-周红伟
linux·网络·人工智能·腾讯云·openclaw
大卡片2 小时前
linux和IO常见面试题
linux·运维·服务器