线程概念和线程控制

每天一个优秀提示词学习收藏 - 文本创作篇(二) 宝子们,今天不藏私啦!我把这段时间四处搜罗、潜心钻研出来的文本创作 Prompt 提示词大礼包双手奉上,这可都是能让你的 AI 创作水平原地起飞的干货!走过路过不要错过,赶紧码住,开启你的 AI 创作进阶之旅吧! 阅读详情

1. 线程概念

什么是线程?

举个“住房”的例子理解:

  • 传统的进程(单线程):像一个人独居
    • 他拥有整个房子(虚拟地址空间)
    • 客厅、厨房、厕所(代码段、数据区、堆区)都是他一个人的
    • 他做饭、扫地、洗衣服(CPU调度执行)一件事一件事做
  • 多线程进程:多人合租
    • 资源共享: 大家住在这个房子里(共享同一个地址空间、页表、文件描述符)。有人在客厅放个杯子(全局变量),其他人都能看见并修改
    • 独立调度: 每个租客都是独立的个体,A在看电视(线程A),B在做饭(线程B),互不干扰
    • 私有空间: 大家共用房子,每个人有自己的房间(栈空间)和记事本(寄存器上下文)–>记住自己刚才干到哪一步了

一个进程创建伴随着进程控制块(PCB)、进程地址空间(mm_struct)以及页表的创建

image-20260124140205005

每个进程都有自己独立的虚拟地址空间和独立的页表,意味着进程在运行时具有独立性

站在Linux内核角度理解:到底有没有线程?

Linux中没有真正意义上的线程

Windows VS Linux:

  • Windows:内核中真正定义了进程描述符和线程描述符两个数据结构
  • Linux:内核设计者觉得不需要这么复杂,线程本质就是共享了资源的进程,那么可以复用大量已经实现的代码

Linux的实现方式:轻量级进程(LWP)

Linux内核眼里,CPU只看到了task_struct(PCB)

  • 创建普通进程(fork):
    • 复制一个task_struct
    • 复制一张全新的虚拟地址空间(页表指向新的物理内存—>这种情况还是发生数据修改写时拷贝才做的)
    • 关系:独立、各过各的、老死不相往来
  • 创建线程(clone/pthread_create):
    • 复制一个**task_struct**
    • 但是!不复制虚拟地址空间,而是让新的**task_struct指针指向和旧的一样的mm_struct!**
    • 关系:肉体(PCB)独立,灵魂(内存视角)共享

image-20260124140518474

我们创建了3个线程,可以总结出:

  • 每个线程都是当前进程里的一个执行流
  • 线程在进程内部运行,本质是线程在进程地址空间内运行,进程申请的资源,几乎被所有线程共享

重新定义进程和线程

  • 进程是分配系统资源的基本单位—>一间房子,属于一栋楼系统的一份资源实体
  • 线程是系统进行CPU调度的基本单位—>一间房子里的每个租客

image-20260124140931718

红框整个整体是进程,当然进程还有文件、信号等其他控制块---->我们之前的定义没有错:进程=内核数据结构+代码数据

总结:谈一下Linux下的进程和线程区别

从用户视角来看,线程是进程内部的执行流

但从Linux内核视角来看,其实并没有严格区分进程和线程。Linux 将它们统一实现为 task_struct,所以CPU调度时,不关心当前调度的task_struct是进程还是线程,只关心一个一个的独立执行流

所谓的线程,在Linux内核中被称为轻量级进程 (LWP)。它们的本质区别在于 clone 时传入的标志位不同

  • 如果是普通进程,拥有独立的虚拟地址空间 (mm_struct)
  • 如果是线程,多个 task_struct 会指向同一个 mm_struct,共享代码段、数据段和堆

所以,在Linux下,CPU调度时只看 task_struct,看到的都是一个个独立的执行流,这使得Linux的线程调度非常高效,不需要维护复杂的层级关系


2. 线程控制

创建线程

代码

#include <iostream>
#include <string>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>

// 结构体传参
struct ThreadData{
    int thread_id;
    std::string msg;
};

std::string ToHex(pthread_t tid){
    char buf[64];
    snprintf(buf, sizeof(buf), "0x%lx", tid);
    return buf;
}

// 新线程入口函数
void* thread_routine(void* arg){
    // 参数是任意类型 按照我们需要传递的类型强转
    ThreadData* data = static_cast<ThreadData*>(arg);

    // 获取当前线程的ID
    pthread_t tid = pthread_self();

    std::cout << "新线程启动!库ID:" << ToHex(tid)
              <<" | 业务ID:" << data->thread_id
              <<" | 消息:" << data->msg << std::endl;

    sleep(3);

    std::cout << "新线程任务完成,准备退出...\n";

    std::string* result = new std::string("success");

    return (void*)result;

}

int main(){
    std::cout << "主线程开始运行, pid: " << getpid() << std::endl;

    pthread_t tid;  // 新线程ID
    // C++11列表初始化
    ThreadData* tdata = new ThreadData { 1, "hello from thread!"};
    // 参数1 线程ID指针->输出型参数
    // 参数2 线程属性->nullptr是默认属性
    // 参数3 线程函数->函数指针
    // 参数4 传给线程函数的参数->任意类型
    int ret = pthread_create(&tid, nullptr, thread_routine, (void*)tdata);
    if(ret != 0){
        std::cerr << "线程创建失败,错误码:" << ret <<std::endl;
    }

    std::cout << "主线程创建新线程成功,新线程ID: " << tid << std::endl;
    std::cout << "主线程做我的事情...\n";

    void* thread_result;
    std::cout << "主线程阻塞等待新线程结束...\n";
    pthread_join(tid, &thread_result);

    std::string* res_str = static_cast<std::string*>(thread_result);
    std::cout << "主线程等待新线程退出成功,返回值:" << *res_str << std::endl;

    delete tdata;
    delete res_str;

    return 0;
}

函数接口

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);
  • 参数:
    • thread:线程返回ID
    • attr:线程属性,nullptr表示默认属性
    • start_routine:线程启动入口函数,函数指针
    • arg:传给入口函数的参数,可以是任意类型
  • 返回值:
    • 成功返回0
    • 失败返回错误码

参数4为什么传void*?

为了通用性,C语言没有模板,OS不知道要传什么类型,使用void*表示接收任何类型的参数

注意: 一定不能传栈变量的地址,如果主线程比子线程先结束,或者主线程进入了下一个循环,栈里的变量销毁了,子线程读到的就是垃圾数据,一定要用new在堆上开辟数据,或者确认主线程生命周期长于子线程

使用指令查看线程信息

img

  • LWP得到的是真正的线程ID
  • 在ps -aL得到线程ID后,有一个线程ID和进程ID相同,这个线程就是主线程,主线程的栈在栈区,其他线程的栈在共享区,因为pthread系列函数是pthread库提供的,动态库加载在共享区

怎么解释pthread_self()打印出来的ID?

  • 打印出来的是一个很大的数字->地址,这个ID是用户态线程库维护的ID,本质是该线程在库中元数据(线程ID、线程栈、寄存器)的内存首地址
  • 想要获取内核态的LWP ID需要gettid()

原理剖析

原理一:Linux里没有真正的线程

在Linux内核看来,线程就是进程,并没有为线程专门定义一个struct thread,依然使用struct task_struct

调用pthread_create时,底层最终调用的时clone()系统调用:

// 伪代码演示 clone 的调用
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, ...);

CLONE_VM (Virtual Memory):

  • 含义:新老进程共享内存描述符 (mm_struct)。
  • 效果:这就是为什么在主线程 new 了一个对象,子线程能直接通过指针访问!因为它们的**页表(Page Table)**指向的是同一块物理内存

CLONE_FILES:

  • 含义:共享文件描述符表。
  • 效果:主线程打开了 log.txt,子线程直接能写,不需要重新 open
原理二:线程独有资源VS共享资源
共享资源 (体现轻量级)独有资源 (体现独立调度)
地址空间 (代码段、数据段、堆)栈 (Stack):每个线程必须有独立栈来保存自己的局部变量和函数调用链
文件描述符表 (打开的文件)寄存器上下文 (PC指针、SP指针):线程切换的核心就是切寄存器
信号处理方式 (Handler)线程 ID (LWP)
当前工作目录信号屏蔽字 (Signal Mask)
原理三:局部性原理的应用

线程共享同一块虚拟地址空间,CPU在切换线程时,不需要切换页表(CR3寄存器不变),这意味着TLB中的缓存依然有效,Cache命中率极高

销毁线程

代码

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdlib>

using namespace std;

// 方式一 自然死亡
void* thread_return(void* arg){
    cout << "线程1 正在运行,准备通过return退出...\n";
    sleep(1);
    return (void*)10;
}

// 方式二 自杀 pthread_exit
void* thread_exit_self(void* arg){
    cout << "线程2 正在运行,准备通过phread_exit退出...\n";
    sleep(1);
    sleep(3);
    // 立即终止当前线程 后续代码不会执行
    pthread_exit((void*)20);

    cout << "线程2 这行代码不会被执行!\n";
    return nullptr;
}

// 方式三 他杀 pthread_cancel
void* thread_cancel(void* arg){
    cout << "线程3 正在死循环运行,等待被主线程取消...\n";
    while(1){
        // 这里的sleep是个取消点 只有遇到取消点 线程才会响应cancel信号
        sleep(1);
        cout << "线程3 还活着...\n";
    }
    return nullptr;
}

// 致命陷阱
void* thread_trap(void* arg){
    cout << "陷阱线程 如果我调用exit(1) 整个进程都会挂掉!\n";
    // exit(1);     // 取消注释 会发现整个程序直接退出 后面的log全没了
    return nullptr;
}

int main(){
    pthread_t tid1, tid2, tid3, tid4;
    void* ret_val;      

    pthread_create(&tid1, nullptr, thread_return, nullptr);
    pthread_create(&tid2, nullptr, thread_exit_self, nullptr);
    pthread_create(&tid3, nullptr, thread_cancel, nullptr);
    // pthread_create(&tid4, nullptr, thread_trap, nullptr);


    cout << "==============================================" << endl;
    pthread_join(tid1,&ret_val);
    cout << "主线程 等待线程1退出,返回值:" << (long long)ret_val << endl;
    pthread_join(tid2,&ret_val);
    cout << "主线程 等待线程2退出,返回值:" << (long long)ret_val << endl;
    
    cout << "主线程 请求取消线程3...\n";
    pthread_cancel(tid3);
    pthread_join(tid3,&ret_val);
    // 如果线程是被 cancel 的
    // join 得到的返回值是一个宏 PTHREAD_CANCELED (通常是 -1)
    if(ret_val == PTHREAD_CANCELED)
        cout << "主线程 线程3成功被取消...\n";
    else
        cout << "主线程 线程3异常退出...\n";

    cout << "==============================================" << endl;
    cout << "主线程 所有演示结束\n";

    return 0;

}

函数接口

pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
  • 功能:终止调用该函数的线程
  • 细节:
    • 释放线程独有的资源,但不会释放进程共享的资源

在main函数中调用pthread_exit会发生什么?

  • 直觉:main结束,进程就结束
  • 事实:如果在main结尾调用pthread_exit而不是return或exit,主线程会终止,但只要有其他子线程在运行,进程依然活着
#include <iostream>
#include <pthread.h>
#include <unistd.h>

using namespace std;

// 子线程函数:它要活得比主线程长
void* worker(void* arg) {
    for (int i = 1; i <= 5; i++) {
        sleep(1);
        cout << "[子线程] 我还在运行... 第 " << i << " 秒" << endl;
    }
    cout << "[子线程] 任务完成,我也要退出了。此时进程才会真正结束。" << endl;
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, worker, NULL);

    cout << "[主线程] 我只负责启动子线程,现在我要先走了!" << endl;

    // 重点来了!!!
    // 如果这里写 return 0; 或者 exit(0); 子线程会瞬间被杀掉。
    // 但是我们用 pthread_exit,只结束主线程自己。
    pthread_exit(nullptr);

    // 这行代码永远不会执行
    cout << "这行代码绝对看不见!" << endl;
    
    return 0;
}

输出:

[主线程] 我只负责启动子线程,现在我要先走了!
[子线程] 我还在运行... 第 1 秒
[子线程] 我还在运行... 第 2 秒
[子线程] 我还在运行... 第 3 秒
[子线程] 我还在运行... 第 4 秒
[子线程] 我还在运行... 第 5 秒
[子线程] 任务完成,我也要退出了。此时进程才会真正结束。

原理剖析:

  • 原理一:return/exit VS pthread_exit的区别
    • return 0(main中)/exit:
      • 调用系统调用exit_group()
      • 含义:把这个进程组里的所有人全都干掉,回收内存,关闭文件
    • pthread_exit():
      • 调用系统调用exit()
      • 含义:只把当前这个调度实体的状态设为死亡,不要动共享资源(内存、文件描述符)
  • 原理二:进程退出的判断标准
    • 隶属于这个进程组的最后一个线程退出时,进程退出
    • Linux内核维护了一个计数器:
      • 程序启动,cnt = 1->主线程
      • pthread_create,cnt = 2->主+子
      • 主线程调用pthread_exit,cnt--->cnt = 1
        • **此时cnt不为0!内核认为进程依然活着!mm_struct**依然保留!
      • 子线程运行结束,cnt = 0
        • 内核检测到归零,彻底回收进程资源
pthread_cancel
#include <pthread.h>
int pthread_cancel(pthread_t thread);
  • 功能:向指定线程发送终止请求
  • 底层细节(取消点)
    • 这是一个异步请求,调用了cancel,对方不一定马上死
    • 目标线程必须运行到某个系统调用(如sleep、read、write)时,检测到取消信号,才退出
    • 不推荐使用

return VS exit VS pthread_exit

函数作用范围影响
return (在线程函数中)仅终止当前线程最安全、最推荐的方式
pthread_exit仅终止当前线程可以在函数深层嵌套中直接退出线程,无需层层 return
exit()终止整个进程核武器。任何一个线程调用 exit,所有线程(包括主线程)全部瞬间蒸发,内存全部回收

线程是怎么死的?

资源的释放:

  • 当线程终止时,内核会将该线程的状态设置为 EXIT_ZOMBIE(僵尸状态)
  • 它的栈和 task_struct 结构体依然保留
  • 为什么要保留?因为要存返回值! 等着别人来取(Join)

pthread_join 的本质:

  • pthread_join 本质上是在等待目标线程的 task_struct 变成 EXIT_ZOMBIE
  • 一旦等到,主线程会把该线程的返回值(存储在 task_struct 的某个字段里)拷贝出来,然后通知内核:“你可以把这个线程彻底回收了(释放 task_struct)”
  • 如果不 join,这块 task_struct 内存就泄露了(内存泄漏)

等待线程

代码

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdlib>

using namespace std;

// 场景1:子线程计算数据 返回一个结构体指针
struct Result{
    int code;
    string msg;
};

void* thread_work(void* arg){
    long long id = (long long)arg;
    cout << "子线程 " << id << " 开始工作...\n";
    
    sleep(2);

    // 返回值必须在堆区开辟!!!或者使用静态/全局变量
    // 绝对不能返回局部变量地址!!!函数结束栈帧就销毁了
    Result* res = new Result();
    res->code = 200;
    res->msg = "success";

    cout << "子线程 " << id << " 工作完成,返回结果地址:" << res << endl;

    return (void*)res;
}

// 场景2:子线程被异常取消
void* thread_bad(void* arg){
    cout << "子线程bad 我来打酱油,马上被cancel...\n";
    while(1) sleep(1);
    return nullptr;
}

int main(){
    pthread_t tid_work, tid_bad;

    pthread_create(&tid_work, nullptr, thread_work, nullptr);

    cout << "主线程 正在阻塞等待子线程完成...\n";

    void* ret_ptr = nullptr;
    int n = pthread_join(tid_work, &ret_ptr);
    if(n == 0){
        Result* final_res = (Result*)ret_ptr;
        cout << "主线程 成功回收工作线程!\n";
        cout << "   |--返回地址: " << ret_ptr << endl;
        cout << "   |--状态码: " << final_res->code << endl;
        cout << "   |--消息: " << final_res->msg << endl;

        delete final_res;
    }

    cout << "------------------------------------------" << endl;
    pthread_cancel(tid_bad);
    pthread_join(tid_bad, &ret_ptr);
    if(ret_ptr == PTHREAD_CANCELED)
        cout << "主线程 检测到子线程时被强制取消的!\n";

    return 0;
}

函数接口

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
  • 功能:以阻塞等待的方式等待指定线程结束

  • 参数一:要等的哪个线程ID

  • 参数二

    • 类型void**指向void指针的指针
    • 作用:存储子线程的返回值
  • 为什么要二级指针?

    1. 子线程的函数定义是void* func(void* arg),返回一个void*(比如指向堆内存的地址0x1234)
    2. 主线程想要拿到这个地址0x1234
    • 如果只传void*,同样都是一级指针,值传递,函数内部修改不了外部变量的值
    • 举个例子:想在函数内部修改一个int,需要传int*
    • 推导:而现在想要在函数内部修改一个void*,需要传void**

线程是如何阻塞和唤醒的?

  1. 调用join
    • 主线程调用 pthread_join
    • 内核检查目标线程 tid 的状态
    • 如果目标线程还在运行,主线程将自己的状态从 TASK_RUNNING 改为 TASK_INTERRUPTIBLE (可中断睡眠状态)
    • 主线程被放入一个等待队列 (Wait Queue),移出 CPU 运行队列。此时主线程卡死,不占用 CPU
  2. 子线程结束
    • 子线程运行完毕,调用pthread_exit
    • 内核讲子线程状态设为EXIT_ZOMBIE
    • 内核向等待队列发信号,唤醒主线程
  3. 资源回收
    • 主线程醒来,恢复TASK_RUNNING
    • 主线程读取子线程task_struct中保存的退出码->void*返回值,填入void** retal里
    • 内核释放子线程的task_struct和内核栈,子线程彻底消失

分离线程

代码

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <cstring>
using namespace std;

// 场景1:子线程自己分离自己
void* thread_self_detach(void* arg){
    // 获取自己的ID并分离
    pthread_detach(pthread_self());

    cout << "[主动独立线程] 我已经自我分离,运行2秒后自动消失,不用管我...\n";
    sleep(2);
    cout << "[主动独立线程] 拜拜!\n";
    return nullptr;
}

// 场景2:被主线程分离
void* thread_passive(void* arg){
    cout << "[被动独立线程] 我被主线程踢开了,运行1秒...\n";
    sleep(1);
    return nullptr;
}

int main(){
    pthread_t tid1, tid2;
    // 方式一:子线程内部自我分离
    pthread_create(&tid1, nullptr, thread_self_detach, nullptr);
    // 尝试join一个分离线程
    sleep(1);
    int ret = pthread_join(tid1, nullptr);
    if(ret != 0){
        // 分离成功 join应该失败
        cout << "[主线程] join 线程1 失败 符合预期! 错误码: " << ret << endl;
    }else{
        cout << "[主线程]成功join 说明还没分离!\n";
    }

    cout << "================================================\n";
    
    // 方式二:主线程主动分离子线程
    pthread_create(&tid2, nullptr, thread_passive, nullptr);

    cout << "[主线程] 主动分离线程2...\n";
    pthread_detach(tid2);
    // 再次尝试join
    ret = pthread_join(tid2, nullptr);
    if(ret != 0){
        cout << "[主线程] join 线程2 失败 符合预期! 错误码: " << ret << endl;
    }

    cout << "==========================================\n";
    cout << "[主线程] 我任务结束了 为了杀死在运行的线程 使用pthread_exit退出\n";

    pthread_exit(nullptr);
}

函数接口

#include <pthread.h>
int pthread_detach(pthread_t thread);
  • 功能:将指定线程的属性从Joinable可连接修改为Detached分离
  • 参数:线程ID
  • 返回值:成功返回0,失败返回错误码

注意:

  • 默认状态: 线程创建时,默认是可连接状态,必须有人去join它,否则会资源泄漏
  • 不可逆: 一旦线程被分离,就恢复不到可连接状态了
  • 冲突: 对一个已经分离的线程调用pthread_join,会直接返回错误码EINVAL22,不会阻塞

为什么要分离线程?

  • 资源自动回收
    • Joinable线程:当线程退出时,内核会保留它的task_struct和部分内核栈,编程僵尸态,等待主线程回收
    • Detached线程:告诉内核:“我没有返回值,不需要管我”,分离线程结束之后,内核直接调用资源释放函数,彻底回收它的task_struct和页表映射
  • 局部性原理和并发模型
    • 在客户端/服务器中
      • 主线程只负责连接
      • 来一个连接,创造一个worker线程
      • 主线程立刻回来监听下一个请求,根本不关心worker什么时候结束,也不关心返回值
      • 此时必须使用detach,利用OS的机制自动回收资源

3. 从关键源码理解线程

clone系统调用的flag

Linux内核里,没有线程的概念,线程只是共享了大部分资源的进程(LWP)

在 C++ 里调用 pthread_create 时,底层最终调用了内核的 clone

// Glibc 源码片段 (nptl/sysdeps/pthread/create_thread.c)

static int
create_thread (struct pthread *pd, const pthread_attr_t *attr,
               bool *stopped_start, STACK_VARIABLES_PARMS, bool *thread_ran)
{
  // ... 前面一堆准备工作 ...

  // 重点来了!!!这就是调用内核 clone 系统调用的地方
  // 看这些标志位 (CLONE_*),这就是线程和普通进程的区别!
  const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND
                           | CLONE_SETTLS | CLONE_PARENT_SETTID
                           | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
                           | 0);

  // 调用系统调用
  int res = INLINE_CLONE_SYSCALL (clone_flags, pd, ...); 
  
  // ...
}

对于CLONE_VM,它决定了代码段、数据段、堆是共享的

在新老进程(线程)的task_struct中,mm指针(mm_struct)指向同一个内存描述符对象,大家公用一张页表

对于CLONE_FILES,多个线程能读写同一个文件

为什么线程切换比进程快?

pthread_create底层调用clone时代了CLONE_VM标志,内核不需要为新线程复制新页表,新旧线程指向同一个mm_struct,页表没变,CPU的TLB就不需要刷新,缓存命中率极高

线程ID是啥 PID vs LWP

在 Linux 内核里,没有 struct thread,只有 struct task_struct(进程描述符)

// Linux Kernel 源码片段 (include/linux/sched.h)

struct task_struct {
    // ...
    
    // 1. 真正的“调度 ID” (LWP ID)
    // gettid() 返回的就是这个。操作系统调度是用这个 ID。
    pid_t pid; 

    // 2. 线程组 ID (进程 ID)
    // getpid() 返回的是这个!
    // 同一个进程下的所有线程,这个 tgid 都是一样的(等于主线程的 pid)。
    pid_t tgid; 

    // 3. 这里的 mm 就是内存描述符
    // 所有线程的 mm 指针都指向同一个内存块!
    struct mm_struct *mm; 

    // ...
};
  • PID:在用户态叫做进程ID,内核源码里叫tgid线程组ID
  • LWP:内核里叫pid
  • 结论:多线程程序中,每个线程有自己独立的task_struct,并且pid不同,但是tgid相同

栈与寄存器

线程栈在物理上是存在于共享的虚拟地址空间的,但是逻辑上独享一块栈空间

// Glibc 源码片段 (nptl/allocatestack.c)

static void *
allocate_stack (const pthread_attr_t *attr, struct pthread **pdp,
                ALLOCATE_STACK_PARMS)
{
  // ... 计算栈的大小 ...
  size_t size = attr->stacksize;

  // 重点:使用 mmap 在堆(或者映射区)中申请一块内存作为栈
  mem = mmap (NULL, size, prot,
              MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);

  // ... 
  
  // 将这块内存的高地址部分,作为线程的栈底 (Stack Base)
  // 因为栈是向下生长的
  pd->stackblock = mem;
  pd->stackblock_size = size;
  
  // ...
}

每个线程有一块独立的内存区域作为栈,用来存放局部变量】函数调用链。如果部队里,两个线程同时调用func,压栈出栈就会乱

寄存器是真正的硬件独享,也是线程调度的本质

struct thread_struct {
    unsigned long sp; // Stack Pointer (栈指针)
    unsigned long ip; // Instruction Pointer (指令指针/PC)
    // ... 其他通用寄存器 ...
};

CPU只有一套寄存器,但是每个线程在内存里都存了一份自己的信息

再次梳理共享和独享资源

线程共享资源 (由 CLONE_VM 等标志位决定)
  1. 进程 ID (PID/TGID):大家表面上都属于同一个进程
  2. 进程的虚拟地址空间:
    • 代码段 (.text):大家执行同一份代码
    • 数据段 (.data/.bss):全局变量、静态变量是共享的(最容易发生数据竞争的地方!)
    • 堆 (Heap):new/malloc 出来的内存,指针传递给谁,谁就能用
  3. 文件描述符表:打开的文件(fd)、网络套接字(Socket)
  4. 信号处理方式 (Signal Disposition):
    • 如果在主线程设了 SIGINT 的处理函数,子线程收到信号也会调这个函数。
  5. 用户 ID (UID) 和组 ID (GID)
  6. 当前工作目录
线程独享资源 (为了独立调度和运行)
  1. 线程 ID (LWP ID):内核调度的唯一标识
  2. 寄存器上下文 (Context):
    • SP (栈指针):指向自己的栈。
    • PC (程序计数器):记住自己执行到哪行代码了
  3. 栈 (Stack):存放局部变量、函数参数、返回地址
  4. 错误码 (errno):
    • 重点! errno 是全局变量吗?不是!
    • 在多线程 C 语言库中,errno 是一个 Thread Local Storage (TLS) 变量。每个线程都有自己的 errno,否则 A 线程出错覆盖了 B 线程的错误码,就乱套了
  5. 信号屏蔽字 (Signal Mask):
    • 可以让线程 A 屏蔽 SIGINT,但线程 B 不屏蔽
  6. 调度优先级

4. 总结

在这里插入图片描述

UE5Could not be compiled. Try rebuilding from source manually. 阅读详情

相关推荐

【扫地机器人】详细介绍扫地机器人软硬件测试和场景化测试

本文提出2025版扫地机器人全生命周期测试方案,涵盖实验室仿真、硬件台架、客户实景等全流程验证。测试以"清洁率≥95%、覆盖率≥98%、MTBF≥1000h"为核心指标,采用数字孪生与120套真实场景结合的双V模型。重点包括:传感器精度(LDS误差<±10mm)、清洁系统性能(10g粉尘吸入率≥99%)、动力续航(800次循环容量保持≥80%)等硬件测试;以及SLAM重定位(误差<5cm)、AI识别(mAP>92%)等软件验证。方案特别针对宠物家庭(毛发缠绕率≤3%)、

2501_92897788的博客 2288

线程的概念和控制

我们使用的所有的线程的函数都不是系统直接提供的,是原生线程库提供的,而原生线程库一定不只会有我们一个进程用,所以原生线程库中一定会存在多个进程创建的多个线程,所以线程库一定要把我们多个进程创建的线程给管理好,所以线程库中会存在描述线程的结构体,结构体中有很多线程的数据(属于哪个进程,线程id等),然后再用数据结构把各个描述线程的结构体管理起来。对上提供线程控制的接口。因为线程是在进程的地址空间中运行的,并且线程创建更简单,只需要复制进程的PCB,只有一小部分的数据是私有的,大部分数据都和进程是一样的。

bushibrnxiaohaij的博客 1779

数据中心机房设备标签规范建议

机房的维护工作中,标识管理是极其重要的一部分,从基本的线缆上跳接信息的标识,到设备、机柜标识规范的实施,不同程度的体现了标识管理的重要作用。本规范适用于机房主设备、线缆、机柜(含PDU插排)、仪器仪表、动力配套设备(各类空调、消防设备、配电柜、UPS等设备)、网络口、电源插座口的标签、标识规定。动力设备的设备编号、设备名称、生产厂商及厂商技术支持热线、设备型号、设备序列号、设备使用部门、设备责任人、设备启用时间及设备保修年限、条形码。各类标签、标识可根据设备和机柜的尺寸、大小进行调整。

有地气的强弱电小圈子 3527

Linux线程-概念和控制

Linux多线程-概念和控制零、前言一、Linux线程概念1、什么是线程2、vfork函数/pthread线程库3、线程优缺点及其他分析二、Linux进程VS线程1、进程和线程三、Linux线程控制1、POSIX线程库2、线程创建3、线程ID及线程地址空间布局4、线程终止5、线程等待6、线程分离 零、前言 本章主要讲解学习Linux中的线程 一、Linux线程概念 1、什么是线程 概念: 在一个程序里的一个执行路线就叫做线程(thread),更准确的定义是:线程是“一个进程内部的控制序列”

~ 2609

腾讯大佬总结的170道 python面试题

简述基于 TCP 协议的套接字通信流程。19.面向对象中`__new__` 和 `__init__` 区别。86. 解释 Python 中的`//`,`%`和`**`运算符。45.isinstance 的作用以及与 type()的区别。3. 简述下 Python 中的字符串、列表、元组和字典。143. 如何高效的找到 redis 中的某个 KEY。140. 如何保证 redis 中的数据都是热点数据。147. redis 中的 watch 的命令的作用。108. 简述 进程、线程、协程的区别以及应用场景。

AudiA6LV6的博客 491

别再折腾虚拟机了!手把手教你用WSL2在Windows 11上安装Kali Linux(含汉化与图形界面)

本文详细介绍了如何在Windows 11上通过WSL2安装和配置Kali Linux,包括汉化与图形界面设置。通过WSL2,用户可以享受接近原生性能的Linux体验,同时保持与Windows系统的无缝集成,特别适合网络安全爱好者和开发者。文章还提供了常见问题解决和性能优化建议,帮助用户高效使用Kali工具集。

weixin_42525369的博客 323

【Linux】线程概念和线程控制

线程是计算机中执行的最小单位。在操作系统中,一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间、文件描述符等。线程是进程中的实际工作单元,它可以独立执行任务,也可以与其他线程共享数据。

javamyfriend的博客 1210

淘特App x-sign参数逆向实战:从抓包到算法定位

本文详细解析了淘特App的x-sign参数逆向过程,从抓包分析到算法定位,涵盖了使用jadx-gui进行静态分析、Frida动态Hook验证以及Native层算法还原等关键技术。文章重点介绍了x-sign参数的生成机制及其在阿里系应用中的安全防护策略,为逆向工程师提供了实用的调试技巧和对抗策略。

5b6n7m8的博客 229

Linux之线程概念,理解和控制

Linux之线程的概念,理解,和进程的区别,异常,用途以及线程的控制

weixin_74285822的博客 1270

Python与PyCharm安装配置教程:从零搭建开发环境

编程入门的第一步是搭建开发环境,而Python解释器与IDE工具的选择常让新手困惑。Python作为解释型语言,负责将代码翻译为机器指令,而PyCharm作为集成开发环境,提供代码编辑、调试与项目管理功能,两者协同工作构成高效开发基础。环境变量PATH的配置是安装过程中的关键环节,正确处理可避免“python不是内部或外部命令”的常见错误。理解pip包管理机制,则能轻松安装第三方库并管理项目依赖。从下载官方安装包到运行第一个Hello World程序,再到利用虚拟环境隔离应用依赖,这套流程适用于Window

benben 685

CEEMDAN算法及其应用

本博文主要介绍CEEMDAN算法的原理及其在信号去噪方面的应用

总结论文中的算法并给出论文的复现代码 6万+

多线程——线程概念和线程控制

什么是线程,POSIX线程库,线程控制:pthread_create线程创建,pthread_exit线程终止,pthread_join线程回收,pthread_cancel线程取消,pthread_detach线程分离。线程id和地址空间分局,C++语言级别的多线程,二次封装线程库

欢迎来到梨+萍的博客,不准备看点啥学习嘛。。。 501

手把手教你学Simulink——基于电机驱动系统的场景实例:永磁同步电机(PMSM)在电动汽车中的矢量控制仿真

随着全球对环境保护的关注增加,电动汽车(Electric Vehicle, EV)因其环保特性而受到广泛关注。永磁同步电机(Permanent Magnet Synchronous Motor, PMSM)由于其高效率、高功率密度和良好的动态响应性能,在电动汽车驱动系统中得到了广泛应用。矢量控制(Field-Oriented Control, FOC)是实现PMSM高性能控制的关键技术之一,它能够实现对电机转矩和磁场的独立控制,从而达到类似直流电机的控制效果。本文将详细介绍如何使用来构建一个。

MHD0815的博客 1万+

线程概念和控制

• 在⼀个程序⾥的⼀个执⾏路线就叫做线程(thread)。更准确的定义是:线程是“⼀个进程内部的控制序列” • ⼀切进程⾄少都有⼀个执⾏线程 • 线程在进程内部运⾏,本质是在进程地址空间内运⾏ • 在Linux系统中,在CPU眼中,看到的PCB都要⽐传统的进程更加轻量化 • 透过进程虚拟地址空间,可以看到进程的⼤部分资源,将进程资源合理分配给每个执⾏流,就形 成了线程执⾏流 如果在没有虚拟内存和分⻚机制的情况下,每⼀个⽤⼾程序在物理内存上所对应的空间必须是连续的。 因为每⼀个程序的代码、数据⻓度都是不⼀样的

2301_79811170的博客 897

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

【Linux】Linux线程概念和线程控制

本文介绍了Linux中线程的概念和线程控制--线程创建,终止,等待,分离

qq_67582098的博客 2605

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Linux多线程——线程的概念和控制

一共有四个参数,第一个是线程ID,第二个是选项,目前只设置为空指针即可,第三个选项是需要让新线程走的执行流,第四个是传递给其中的参数。那么一个进程他的执行路线至少有一个,也就算做是一个线程,这个进程本身也可以创建分支线程,那么原来的这个进程也就叫做主线程。从操作系统课本里我们可能听说过,线程是一个微缩版的进程,他拥有TCB,不会被分配资源,是CPU进行调度的单位。每一种信号的处理方式都是共享的。在CPU的视角来看,他的所调度的所有单位都是PCB,是同一个进程,CPU也无法分辨他运行的是线程还是进程。

ye_yumo的博客 1174

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Linux系统:线程概念和线程控制

而且从上方的struct page源码中可以发现,它是不存储页框的起始地址和终止地址的,因为可以通过下标计算出起始地址,起始地址 + 4kb就可以求出终止地址。可以看到,确实是有两个叫做test.exe的线程的,它们的PID都是141776,但是它们的LWP不同。另外的,你会发现第一个线程的PID = LWP = 141776,说明这个线程是主线程,其余的所有线程都是这个主线程创造的。在CPU调度进程的时候,本质是在调度进程的PCB,把进程的PCB放到运行队列中,然后CPU依次执行队列中的进程。

m0_61088872的博客 1045

linux---线程概念和线程控制

1. 什么是线程 2. 线程和进程的关系 3. 线程的工作原理 4.线程和进程实现并发任务的优、缺 5. 线程控制 什么是线程 在传统的操作系统中,进程就是一个pcb(进行运行中的程序的描述信息),控制程序的运行,但是在linux下没有为线程设计一个pcb来控制线程的运行,线程此时就是以进程pcb模拟实现的,也就是说,线程在linux下pcb实际上是一个线程,在linux下线程以进程pcb模拟实...

boke_fengwei 260

linux线程概念和控制

阐述Linux下线程概念,实现方式,以及线程优缺点,linux下线程控制,包括创建,等待,分离,销毁等

m0_74328241的博客 179

Linux线程概念和控制

线程<=执行流<=进程Linux中的线程模拟进程实现(线程就是轻量级进程)与独立的进程相比,线程创建和销毁的开销较小,因为它们共享相同的内存空间和资源。线程是进程内的执行分支,线程的执行粒度比进程要细(只需要运行一部分代码)我们认为线程是操作系统调度的基本单位(进程内部执行流资源)进程:承担分配系统资源的实体(分给线程)Linux中用进程的内核数据结构模拟线程在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”一切进程至少都有一个执行线程。

2401_84642040的博客 885

Linux线程概念和线程控制

本文重点知识: 1.线程概念: 2.线程之间的资源独有与共享: 3.多线程处理任务的优缺点: 4.线程控制: 一.线程的概念: 1.什么是线程?? 在操作系统中使用pcb来描述一个程序的运行,因此pcb就是一个进程;但是在Linux下没有为线程设计一个pcb来控制线程的运行,因此线程此时就是以进程pcb模拟实现的;Linux下以pcb模拟实现线程,因此Linux下的线程实际上是一个轻量级进程;...

DX_Jone的博客 394
上一篇: 分页式存储管理——内存高效管理的基石
下一篇: 基于抢票系统的线程互斥详解
Vect__
博客等级 码龄1年 204粉丝 74原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值