《拆解 Linux 进程与线程:三个维度讲透二者的区别与联系》

2025-12-18 15:08:25
文章摘要
前引:提到 Linux 并发,绕不开进程与线程的关系。很多人会把线程当成 “迷你进程”,但二者的底层实现、调度机制和使用场景截然不同。本文不堆砌复杂术语,而是从 “定义、资源占用、调度方式” 三个核心维度,用通俗的语言拆解二者的区别与联系,让你彻底明白:线程到底是什么,以及它和进程如何配合支撑 Linux 系统的并发能力!

前引:提到 Linux 并发,绕不开进程与线程的关系。很多人会把线程当成 “迷你进程”,但二者的底层实现、调度机制和使用场景截然不同。本文不堆砌复杂术语,而是从 “定义、资源占用、调度方式” 三个核心维度,用通俗的语言拆解二者的区别与联系,让你彻底明白:线程到底是什么,以及它和进程如何配合支撑 Linux 系统的并发能力!


【一】什么是线程

理解:“进程”有着自己完整的资源(比如代码数据、各个地址的分布...)线程好比进程内的一个个分支,可以调度“进程”内部的资源,来执行对应的分支任务——类比“厂房”中的“工人”利用进程的内部资源来执行不同的任务,之前学习的进程好比一个执行流,也就是一个线程,全部任务都由这个执行流完成;线程越多,该进程的效率越高,对应越复杂,风险越高!
本质:线程是共享进程资源的实体;进程就是分配系统资源的实体

概念:线程是进程的执行单元(或执行流),共享进程的资源、独立运行,本质是“轻量化进程”

【二】进程与线程的切换效率

如果要切换一个进程:

需要销毁对应的全部数据,最终也是去除PCB结构,然后重新加载新的结构数据,形成新的PCB

尤其是“热数据”,这是进程间切换导致效率低的一大“痛点”:最小都有几千KB

“热数据”是需要被高频读取、使用的数据,比如全局变量、函数这些,进程为了避免每次访问都需要去重新加载,就将这些“热数据”放在了Cache(CPU高速缓存区)供CPU快速找到该类型的数据

“热数据”对应着“冷数据”,即哪些访问频率很低的数据,“热数据”需要跟随进程的访问情况随时替换

如果要切换一个线程:

线程是和进程共享资源的,线程的退出不需要去换进程PCB这些,只有进程的退出才去释放PCB,所以线程的替换与退出最极端也是加载一些新的代码数据到CPU执行,影响很低

【三】虚拟到物理地址的转换

在之前我们知道进程地址空间的虚拟地址转换到物理地址需要借助中间的页表,今天我们再深入!

比如现在有一个32位的虚拟地址:X00000001000000010000000100000001

此时虚拟地址会被按照10、10、12位被分隔开:0000000100 0000010000 000000000001

前10位对应“页目录索引”;中间10位对应“页表索引”;后12位对应“业内偏离”

先用前10位确定一级页表的位置,一级页表里面存着二级页表的起始位置(2^10=1024)
中间10位再确定二级页表的具体位置,二级页表中存内存中对应页框位置(2^10=1024)
最后12位再确定页框中具体的字节位置((2^12=4096=4KB))
(后12位:操作系统规定 “一页内存的大小是 4KB”(4096 字节),而 4096 = 2¹²,所以需要 12 位二进制才能表示 “一页内的所有位置”(0 到 4095)比如上面的后 12位 000000000001 换算成十进制是 1,意思是 “在这一页的第 1 个字节位置”)

【四】线程库的认识

首先需要知道系统是提供了“线程库”的,即线程的系统调用接口,但是后来由于线程库的部分接口很复杂,用户二次封装,出现了用户层面二次封装的“线程库”,我们主要学习用户层面的“线程库”

注意:Linux中是没有明确的“线程”的概念的,只有“轻量化进程”的概念,因此上面的线程内核接口实质是轻量化进程的接口。该线程原库名为“Pthread库”,几乎所有Linux平台都已默认携带

【五】线程使用基本常识

(1)任何一个线程如果出现错误会导致进程同时关闭

(2)线程是进程的执行分支,如果单个线程出现异常,操作系统也会向进程发送信号

         导致该程和所有线程关闭

(3)既然线程是进程的执行分支,因此进程退了,该进程的所有线程自然也就崩了

(4)不能使用exit()终止线程,否则会直接导致整个进程结束,它是用来终止进程的

【六】线程库接口

(1)pthread_t

该接口用户返回系统成功创建线程的ID,我们需要创建一个变量接收,例如:tid

pthread_t tid;

i

(注意:此时只是获取了线程ID,并没有创建出来)

此时 tid 就接收了底层系统调用返回的线程 ID,例如:(注意需要指定使用线程库)

​mage


(2)pthread_create()

原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

i

返回值:成功返回0;失败返回错误码(注意错误原因以及错误码都是线程库里面的)

作用:创建一个线程

 参数:

第一个参数:获取的线程ID地址

第二个参数:设置线程的属性(这个后面再谈,先设置为NULL)

第三个参数:线程要执行任务的函数

  1. 函数的返回值是void *(可以返回任意类型的数据,最后要强制转换)
  2. 函数的参数是void *(可以给函数传任意类型的数据,传之前要强制转换)

第四个参数:函数参数

理解:参数

下面我通过讲解普通函数(无参)、普通函数(一个参数)、传对象来教学如何使用这两个接口:

普通函数(无参):

假如现在有这样一个线程的任务函数:

​mage

现在如果要利用pthread_create()创建线程调用该函数,应该这样使用:



解读:第三个参数由系统调用的函数指针指向任务的函数

           第四个参数为任务函数的参数(指针类型),既然不需要函数参数就传NULL

普通函数(有参):

假如现在有这样一个线程的任务函数,需要传递一个字符串作为参数:

解读:该函数的参数需要先强转为 void*,保持与函数原型一致(传参和函数的参数都必须是 void*),随后在使用的时候在强转回来

结构体参数:

假如有下面这样一个结构体对象:(不管怎样第三个参数只能是函数)

struct Person

{

    Person(const char* ptr,int age)

    :_ptr(ptr),_age(age)

    {}


    const char*_ptr;

    int _age;

};


void* thread_task(void* arg)

{

    //注意需要强转回来

    std::cout<<(((struct Person*)arg)->_ptr)<<std::endl;

    std::cout<<(((struct Person*)arg)->_age)<<std::endl;

    //注意函数是有返回值的

    return NULL;

}


解读:不管是传什么参数,都要求指针且必须强转为 void* 类型,与函数原型一致,随后使用参 数的时候再强转回来

理解:返回值

返回值的原理都是一样的:先强转为(void*)指针返回,外面需要有一个接收参数的(void*)指针,同时需要告诉线程返回值存放的位置,拿到之后再强转回来使用

struct Person

{

    Person(const char* ptr,int age)

    :_ptr(ptr),_age(age)

    {}


    const char*_ptr;

    int _age;

};


void* thread_task(void* arg)

{

    //注意需要强转回来

    struct Person* ptr=(struct Person*)arg;


    //注意函数是有返回值的

    return (void*)new struct Person(ptr->_ptr,ptr->_age);

}

 (3)pthread_join()

原型:

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

i

参数:

thread:需要等待的子线程 ID(由 pthread_create 函数返回)

retval:用于存储子线程的返回值(即子线程函数 return 的指针)(无返回值就填NULL)

返回值:

  1. 成功:返回 0
  2. 失败:返回非零错误码

作用:阻塞等待子线程结束并获取其返回值(回收资源)

例如:

​mage

(4)pthread_exit()

原型:

#include <pthread.h>

void pthread_exit(void *retval);

i

参数:指向线程返回值的指针,用于传递线程的退出状态

作用:当前线程会立即停止执行,退出并进入 “终止状态”,并可通过pthread_join获取返回值

例如:

​mage

【七】性质验证

我先形成验证需要的代码:给线程开辟堆空间,用容器来存储结构体指针(形成三个线程为例)

#include<unistd.h>

#include<iostream>

#include<pthread.h>

#include<vector>


#define MAX 3


class My_Pthread

{

public:

    My_Pthread(pthread_t id,int name)

    :_id(id),_name(name)

    {}


    //线程ID

    pthread_t _id=0;

    //线程名

    int _name;

};


//线程任务函数

void* handle(void* arg)

{

    //防止线程跑太快

    sleep(1);

    //强转

    My_Pthread* ptr=(My_Pthread*)arg;


    //std::cout<<"线程编号"<<(ptr->_name)<<"线程ID:"<<(ptr->_id)<<std::endl;


    return NULL;

}


int main()

{

    //用指针来存取线程信息

    std::vector<My_Pthread*> pthread_pointer;


    for(int i=1;i<=MAX;i++)

    {

         pthread_t id;

        //线程执行任务

        My_Pthread* ptr=new My_Pthread(id,i);

        //存储线程数据

        pthread_pointer.push_back(ptr);


        pthread_create(&id,NULL,handle,(void*)ptr);

        ptr->_id=id;

    }


    //线程等待

    for(int i=0;i<MAX;i++)

    {

        pthread_join(pthread_pointer[i]->_id,NULL);

    }

    //回收空间

    for(int i=0;i<MAX;i++)

    {

        delete pthread_pointer[i];

    }


    return 0;

}


声明:该内容由作者自行发布,观点内容仅供参考,不代表平台立场;如有侵权,请联系平台删除。
标签:
语音技术