Linux解锁线程基本概念和线程控制,步入多线程学习的大门(1)

云掣YunChe3个月前技术文章228

1、线程初识

1.1线程的概念

线程是进程内部的一个执行分支,线程是CPU调度的基本单位


那什么是进程呢?


我们之前学习了解到的进程是加载到内存中的程序


进程 = 内核数据结构 + 进程代码和数据。


今天我们要推翻这一观点,该观点是片面的,不正确的!


10001.png


我们之前认为的task_struct就是进程,其实这一个个的task_struct是我们的进程的执行流!!!那进程究竟是什么呢?


进程其实是包含文件描述符表,pcb,页表等等,上面框的一整套才是真正的进程!


1.2.关于线程和进程的进一步理解

我们要抛弃之前的想法,进程不仅仅是PCB数据结构。而是一整套资源的时候,我们就应该清楚进程创建成本很高。原因就是:创建进程时还需要构建文件描述符,信号表,PCB,页表,等等这就会造成空间和时间的浪费。


从内核来看:进程本质上是是一个容器,承担分配系统资源的基本实体,包括执行流资源、地址空间资源、页表映射关系,代码和数据这一整套的资源。


线程只是进程当中的一个执行分支~


1.3.线程的设计理念

线程我们一般称为tcb(进程是pcb),对于线程来说,也一定要和进程一样需要对应操作方法:新建,暂停 ,销毁,调度。那我们如何对线程进行这些操作呢?


如果我们要设计线程,OS也要对线程进行管理。(先描述,再组织)


Linux的设计者认为,进程和线程都是执行流,具有极度的相似性,没必要单独设计数据结构和算法,直接复用代码,所以Linux是用进程模拟的线程!


1.4.进程vs线程(图解)

10002.png


横向的是进程,纵向的是执行流。我们以前讲的进程,是今天讲的特殊情况


1.5地址空间的第四谈

如何从页表定位到物理地址?


页目录保存的是二级页表的地址


10003.png


以后我们在查页表的时候,先拿虚拟地址中的前10位,查页目录,选择具体哪一个页表,然后再查询虚拟地址的中间10位就能找到页框,就肯定能找到一个物理地址,最后我们拿着虚拟地址的最后12位,就是页内偏移,因此就直接找到物理内存中的地址了,有页框的物理初始地址,后面还有虚拟地址后12位的页内偏移就可以定位一个物理地址了!


页表里面存储的是页框的物理地址。


给不同的线程分配不同的区域,本质就是让不同的线程,各自看到全部页表的子集!


2.线程的控制:

2.1.关于线程控制的前置知识

在学习之前,我们要清楚关于创建新线程的前置知识——POSIX线程库。


与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的

要使用这些函数库,要通过引入头文<pthread.h>

链接这些线程函数库时要使用编译器命令的“-lpthread”选项(makefile文件要加上-lpthread”选项)

用户层面:用户知道“轻量级进程”这个概念吗?并没有,只有线程和进程,


系统层面:将轻量级进程的系统调用进行封装,转成线程相关的接口提供给用户


内核层面:Linux到底有没有真线程呢?没有,Linux只有轻量级进程。Linux系统,不会有线程相关的系统调用,只有轻量级进程的系统调用


新线程和主线程,谁先运行呢?不确定,由调度器决定


2.2创建线程的系统调用:


 


这个几号手册具体代表的什么含义?

这个3就是代表他在3号手册中


这种几号手册,一般来说,1号手册都是一些命令,2号手册都是系统调用,3号手册就是C库函数。就是分门别类放置不同的东西。


功能:创建一个新的线程

原型

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

参数:

thread:返回线程ID

attr:设置线程的属性,attr为NULL表示使用默认属性

start_routine:是个函数地址,线程启动后要执行的函数

arg:传给线程启动函数的参数

返回值:成功返回0;失败返回错误码


代码:


#include <iostream>

#include <pthread.h>

#include <unistd.h>

 

// 新线程

void *threadStart(void *args)

{

    while (true)

    {

        std::cout << "new thread running..." << std::endl;

        sleep(1);

    }

}

 

int main()

{

    pthread_t tid;

    pthread_create(&tid, nullptr, threadStart, (void *)"sthread-ew");

 

    // 主线程

    while (true)

    {

        sleep(1);

        std::cout << "main thread running..." << std::endl;

    }

    return 0;

}

当我们进行make编译的时候,会报错:(.text+0x1b): undefined reference to main线程未定义,之所以会出错是因为Linux下使用线程需要引用线程库。必须在makefile文件中加上-lpthread,因为线程库里面是函数。编译器并不认识,不是C语言/C++自带的,而是Linux自己创建的原生线程库文件


10006.png


再加上线程库之后,就可以正常运行了。


10007.png


可以看见,主线程和新线程是可以同时运行的!并且主线程和新线程用的是同一个pid。


所以这两个虽然都是不同的执行流,但是是属于同一个进程内部的,我们可以使用ps -al来查看不同的线程信息


截图_20241118164814.png


这个pid是对应进程的pid,这个LWP其实就是这个线程的id!!!


2.3.线程终止

同一个进程内的线程,大部分的资源都是共享的,地址空间是共享的!


主线程退出 == 进程退出 == 所有线程都要退出 

10008.png



多线程代码往往健壮性不好。进程之间是独立的,不能共享资源哦。但是线程可以共享资源哦!


10009.png

我们怎么没有像进程一样获取线程退出的退出信号呢?

因为线程出异常了,会将整个进程退出,根本没有机会读到pthread_read的退出信号,因此该函数并不考虑退出信息。但是父进程会接受子进程退出时的异常信息


10010.png


如果需要只终止某个线程而不终止整个进程,可以有三种方法:


 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。

线程可以调用pthread_ exit终止自己。

一个线程可以调用pthread_ cancel终止同一进程中的另一个线程

不能用exit终止线程,因为他是终止进程的!


pthread_ exit函数


功能:线程终止

原型

void pthread_exit(void *value_ptr);

参数

value_ptr:value_ptr不要指向一个局部变量。

返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)


pthread_ cancel函数


功能:取消一个执行中的线程

原型

int pthread_cancel(pthread_t thread);

参数

thread:线程ID

返回值:成功返回0;失败返回错误码

相关文章

Helm部署

Helm部署

1、helm介绍在没使用helm之前,向Kubernetes部署应用,需要依次部署deployment、svc等,步骤教繁琐,况且随着很多项目微服务化,复杂的应用在容器中部署以及管理显得较为复杂,he...

SLS日志采集 

SLS日志采集 

创建Pjoject创建日志库接入日志新建机器组数据接入Logtail配置--添加Logtail配置选择需要的日志文本格式按下列步骤完成即可编辑日志格式配置查询分析数据...

开源大数据集群部署(四)Freeipa部署(kerberos+ldap)

开源大数据集群部署(四)Freeipa部署(kerberos+ldap)

1、 FreeIPA介绍Kerberos协议只是一种协议标准的框架,而MIT Kerberos则是实现了该协议的认证服务,是Kerberos的物理载体。将它与Hadoop服务进行集成便能够很好地解决安...

Clickhouse MergeTree原理(二)—— 表和分区的维护

MergeTree是Clickhouse中最核心的存储引擎。上一篇文章中,我们介绍了MergeTree的基本结构。1、MergeTree由分区(partiton)和part组成。2、Part是Merg...

数据湖技术之iceberg(八)Spark与Iceberg整合DDL操作

数据湖技术之iceberg(八)Spark与Iceberg整合DDL操作

1.CREATE TABLE 创建表Create table 创建Iceberg表,创建表不仅可以创建普通表还可以创建分区表,再向分区表中插入一批数据时,必须对数据中分区列进行排序,否则会出现文件关闭...

SQL隐式转换导致索引失效_函数

SQL隐式转换导致索引失效_函数

一、隐式转换分类1.函数2.数据类型3.字符集4.校验规则二、常见案例本节将会针对第一部分提到的四种隐式转换内容,举例说明。1.索引列使用函数导致索引失效示例 SQL 如下,该 SQL 的 where...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。