【Linux】线程篇Ⅰ:线程和task_struct 执行流的理解、相关接口命令、线程异常、线程的私有和共享

news2024/10/7 2:27:41

线程Ⅰ

  • 一、概念
    • 0. 线程
    • 1. 线程的优缺点
    • 2. 页框和页帧
    • 3. 页表的设计、虚拟地址的解析方式、以及块为什么设计成 4kb
    • 4. 对进程的一些整体理解
  • 二、一些接口 和 命令
    • 1. ps -aL - - 查看执行流
    • 2. pthread_create 函数:创建线程
    • 3. ptread_join 线程等待
    • 4. ptread_exit 线程退出
    • 5. ptread_cancel 线程取消
    • 6. ptread_self 线程名称
    • 7. pthread_detach 线程分离
    • 7. 使用举例
  • 三、线程异常
  • 四、进程 VS 线程
    • 1. 线程各自所有
    • 2. 线程共享部分


一、概念

0. 线程

  • 线程是一个执行分支,执行力度比进程更细,调度成本更低
  • 线程是 进程内部的一个执行流
内核观点:
线程 是 CPU 调度的基本单位
进程 是承担分配系统资源的基本实体

概念理解:

  • 调度成本更低,指的是,相较于进程,线程不再需要进行对 cache(高速缓存)

  • 进程是承担分配系统资源的基本实体:进程是一系列资源的集合,包括至少一个 task_struct(执行流)、包括虚拟地址空间、页表、自己的代码和数据等等…内容。

  • 把进程从磁盘加载到内存,也就是加载可执行程序形成进程,可以换一个说法了:把该可执行程序,加载到内存,让 OS 为该进程申请与该进程匹配的所有资源。

  • 之前提到的进程可以理解为:内部只有一个 task_struct(执行流) 的进程。

在这里插入图片描述

操作系统是需要对这么多线程管理的,有些操作系统设计了 TCB (thread control block,线程控制块,属于进程 PCB),调度进程、线程都有各自的调度方法。Windows 就是这样设计的。

  • Linux 内核的设计:复用 PCB 的结构体,用 PCB 模拟线程的 TCB。也就是说,Linux 没有真正意义上的线程,而是用进程方案模拟的线程。

  • 复用代码和结构,使得 Linux 系统更简单,好维护效率更高,也更安全。这也是 Linux 可以不间断的运行的原因,因为一款 OS 操作系统,使用最频繁的功能,除了 OS 本身,接下来就是进程了

每个 pcb 叫做 轻量级进程(light weight process),查询这些线程时,出现的 LWP 就叫做 轻量级进程id

PID 和 LWP 相等,就说明是主线程。操作系统调度的时候,其实不是用 PID 识别进程,而 是用 LWP 进行识别的

1. 线程的优缺点

优点:

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速 I/O 操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用(加密解密、与文件压缩和解压等算法有关的,使用的是 CPU 资源),为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O 密集型应用(下载上传、IO 主要消耗 IO 资源、磁盘的 IO、网络带宽等),为了提高性能,将 I/O 操作重叠。线程可以同时等待不同的 I/O 操作。

ps:对于计算密集型应用,线程不是越多越好,进程 和 线程,与 CPU 的个数 / 核数 一致是比较合适的。

pps:对于 I/O 密集型应用,自然也不是越多越好,但是可以比较多,这个“比较”无法量化,需要结合具体场景进行试验来确定。

缺点:

  • 性能损失:
    • 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  • 健壮性降低:
    • 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
  • 缺乏访问控制:
    • 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
  • 编程难度提高:
    • 编写与调试一个多线程程序比单线程程序困难得多。

2. 页框和页帧

OS 在和磁盘这样的设备进行 IO 交互的时候,绝对不是按照字节为单位的,而是按照 为单位。而磁盘这样的外设工作速度是很慢的,页框 和 页帧 的设计,包括 局部性原理,可以大大提高 IO 的效率

这里的块 我们一般做 4kb 考虑,这是文件系统的要求,具体和 OS 有关。

可以说内存管理的本质,是决定将磁盘中特定的 4kb 块(数据内容)放入到哪一个物理内存的 4kb 空间(数据保存的空间)。

  • 首先 文件系统 + 编译器:注定了文件(可执行程序 + 动态库)在磁盘的时候就是以块(4kb)为单位的。

  • 其次 操作系统 + 内存:内存实际在进程内存管理的时候,也要以 4kb 为单位。

这样 物理内存 中的每 4kb 称作 页page,也叫页框
磁盘 中的每 4kb 称作 页帧

在这里插入图片描述

这其中的 页page,会有一个个 struct page{}; 进行状态标识等,里面属性非常少。这些 page 结构体,会由类似 struct page mem[1,048,576] 这样的数组进行管理,刚好数组下标可以访问到物理内存的每一个页框。

局部性原理:OS 会提前加载正在访问的数据相邻或者附近的数据。我们通过预先加载要访问的数据的附近的数据,来减少未来 IO 的次数,多加载数据进来的本质,就叫做数据的预加载

3. 页表的设计、虚拟地址的解析方式、以及块为什么设计成 4kb

页表储存的核心功能是映射 虚拟地址 和 物理内存 的位置关系,要了解页表的设置方法,首先要知道:虚拟地址不是整体被使用的,而是按照 10 + 10 + 12 比特进行划分的。

页表:页目录 + 页表项,(32 位系统为二级页表方案,64 位系统为三级页表)具体如图示。

  • 虚拟地址的最高 10 位,以供寻找页目录对应的页表项

  • 虚拟地址的次高 10 位,供页表项找到对应的物理内存的页框

  • 虚拟地址的低 12 位,访问一个块的 4kb 中的具体哪一个字节

      虚拟地址对应的物理内存的定位 = 对应页框的起始地址 + 虚拟地址的低 12 个比特位对应的地址数据
      即:定位任意一个内训字节位置 = 页框 + 页内偏移
      		= 基地址 + 偏移量
    
      2^12:0000 0000 0000 ~ 1111 1111 1111
      		[0, 4095]
    

在这里插入图片描述

估算页表的可预见大小:

2^20 = 1MB
1MB * 4  = 4MB 

但是页表不会全部创建,就注定了页表的实际大小远远小于 4MB。

4. 对进程的一些整体理解

我们在实际 malloc 申请内存的时候,OS 只需要在虚拟地址空间上申请就行了,当我们真正访问的时候,OS 查到没有相应页表,向寄存器 MMU 发送缺页中断,OS 接收到这一动作,就会自动去申请或填充页表,并申请具体的物理内存,接着再继续跑我们后面的代码。

我们知道上述字符常量区的数据是只允许读取,不允许修改的。

char *s = "hello world";
*s = 'H';	// err...

原因是,s 里面保存的是指向字符的虚拟起始地址,对其寻址的时候,必定会伴随虚拟到物理的转化(MMU + 查页表的方式),如果对这个操作进行权限审查,就会发现我们只有读权限即写操作是非法的。此时 MMU 发生异常,OS 识别异常,异常转化成信号发送给目标进程,进程在从内核转化成用户态的时候,进行信号处理,收到了终止进程的信号,至此进程被终止。
在这里插入图片描述


二、一些接口 和 命令

1. ps -aL - - 查看执行流

# 查看文件执行流
ps -aL | grep [可执行程序]
# 带上表头,查看文件执行流
ps -aL | head -l && ps -aL | grep [可执行程序]

2. pthread_create 函数:创建线程

#include <pthread.h>

注意:使用本章函数,需要在编译时,声明库,即带上 -lpthread

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

参数 thread:

  • 创建线程的地址

参数 attr:

  • 线程属性,一般设成 nullptr

参数 start_routine:

  • 线程 thread 所要执行的函数

参数 arg:

  • 作为 strat_routine 回调函数的参数,可以传对象、字符串等等

返回值:

  • 成功返回 0,失败返回错误码。

3. ptread_join 线程等待

#include <pthread.h>
  int pthread_join(pthread_t thread, void **retval);

参数 thread:

  • 等待的线程

输出型参数 retval:

  • 给自己创建的 void* retval 取地址。拿到等待线程的相关结果

返回值:

  • 成功返回 0,失败返回错误码。

4. ptread_exit 线程退出

#include <pthread.h>
  void pthread_exit(void *retval);

输出型参数 retval:

  • 拿到等待线程的相关结果

5. ptread_cancel 线程取消

#include <pthread.h>
  int pthread_cancel(pthread_t thread);

参数 thread:

  • 线程名

主线程使用函数,新线程取消导致的退出,失败返回错误码为 -1( PTHREAD_CANCELED),pthread_join 可以拿到。

 #define PTHREAD_CANCELED (void *)-1

6. ptread_self 线程名称

#include <pthread.h>
  pthread_t pthread_self(void);

返回值:

  • 调用这个函数的线程的线程 id

7. pthread_detach 线程分离

一个线程如果被分离,就无法再被 join,如果 join,函数会报错

#include <pthread.h>
  int pthread_detach(pthread_t thread);

参数 thread:

  • 需要被分离的线程名

返回值:

  • 成功返回 0,失败返回错误码

7. 使用举例

🌰使用举例 1:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <ctime>
using namespace std;

void *threadRun(void* args)
{
    const char*name = static_cast<const char *>(args);

    int cnt = 5;
    while(cnt)
    {
    	// 打印线程名称
        cout << name << " is running: " << cnt-- << " obtain self id: " << pthread_self() << endl;
        sleep(1);
    }
	// 线程正常退出
    pthread_exit((void*)2); 

	// 线程被取消会将 -1 传给参数 ret
    // PTHREAD_CANCELED; #define PTHREAD_CANCELED ((void *) -1)
}

int main()
{
	// 1. 创建线程
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");
    // 2. 取消线程
    sleep(3);
   	pthread_cancel(tid);
   	// 3. 等待线程
    void *ret = nullptr;
    pthread_join(tid, &ret);
    cout << " new thread exit : " << (int64_t)ret << "; quit thread: " << tid << endl;
    return 0;
}

输出结果:

在这里插入图片描述

🌰使用举例 2:创建多个线程,每个线程执行 [1, top] 的求和计算

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

#define NUM 10

enum{ OK=0, ERROR };

class ThreadData
{
public:
    ThreadData(const string &name, int id, time_t createTime, int top)
    :_name(name), 
    _id(id), 
    _createTime((uint64_t)createTime),
    _status(OK), 
    _top(top),
     _result(0)
    {}
    
    ~ThreadData()
    {}
    
public:
    // 输入的
    string _name;
    int _id;
    uint64_t _createTime;

    // 返回的
    int _status;
    int _top;
    int _result;
};


// 线程终止
// 1. 线程函数执行完毕,return void*
// 2. pthread_exit(void*)
// 3. pthread_cancel(int id)
void *thread_run(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args);   // static_case<>:类型强转,和括号强转一个意思,但是更安全

    for(int i = 1; i <= td->_top; i++)
    {
        td->_result += i;
    }
    cout << td->_name << " cal done!" << endl;
    
    pthread_exit(td);
    //return td;
}

int main()
{
    // 线程创建
    pthread_t tids[NUM];
    for(int i = 0; i < NUM ;i++)
    {
        char tname[64];
        snprintf(tname, 64, "thread-%d", i+1);
        ThreadData *td = new ThreadData(tname, i+1, time(nullptr), 100+5*i);
        pthread_create(tids+i, nullptr, thread_run, td);
        sleep(1);
    }


    // 线程等待(并获取新线程退出信息)
    void *ret = nullptr; // int a =  10
    
    for(int i = 0 ; i< NUM; i++)
    {
        int n = pthread_join(tids[i], &ret);
        if(n != 0) cerr << "pthread_join error" << endl;
        ThreadData *td = static_cast<ThreadData *>(ret);
        if(td->_status == OK)
        {
            cout << td->_name << " 计算的结果是: " << td->_result << " (它要计算的是[1, " << td->_top << "])" <<endl;
        }

        delete td;
    }

    cout << "all thread quit..." << endl;
    return 0;
    while (true)
    {
        cout << "main thread running, new thread id : " << endl;
        sleep(1);
    }
}

三、线程异常

  1. 多线程程序中,任何一个线程崩溃了,最后都会导致进程奔溃现象
  • 系统角度:线程是进程的执行分支,线程挂了进程就也挂了。
  • 信号角度:页表转换的时候,MMU识别写入权限的,没有验证通过。MMU 产生异常,0S识别并给进程发信号。之所以称作 Linux 进程信号,是以进程为主的。
  1. 因为执行流看到的资源是通过地址空间看到的,多个 LWP 看到的是同一个地址空间。所以,所有的线程可能会共享进程的大部分资源。也就是我们所说的线程是缺乏访问控制的。

🌰使用举例:

makefile:创建线程,需要在编译的时候导入线程库,添加 -lpthread

threadTest:thread.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f threadTest
#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <ctime>

using namespace std;

void *threadRun(void* args)
{
    const char*name = static_cast<const char *>(args);

    int cnt = 5;
    while(cnt)
    {
        cout << name << " is running: " << cnt-- << " obtain self id: " << pthread_self() << endl;
        sleep(1);
    }

    pthread_exit((void*)11); 

    // PTHREAD_CANCELED; #define PTHREAD_CANCELED ((void *) -1)
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");
    // sleep(3);

    // pthread_cancel(tid);

    void *ret = nullptr;
    pthread_join(tid, &ret);
    cout << " new thread exit : " << (int64_t)ret << "quit thread: " << tid << endl;
    return 0;
}

四、进程 VS 线程

进程是资源分配的基本单位
线程是调度的基本单位

1. 线程各自所有

线程共享进程数据,但也拥有自己的一部分数据:

  • 线程ID
  • 一组寄存器(线程是要被切换的,需要各自的上下文)
  • (线程各自的零食变量不能相互干扰)
  • errno
  • 信号屏蔽字
  • 调度优先级

2. 线程共享部分

进程的多个线程共享 同一地址空间,因此 Text Segment、Data Segment 都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表

  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL 或者自定义的信号处理函数)

  • 当前工作目录

  • 用户id和组id

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/913032.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

5G随身wifi如何选择?简单分类一下

最近5g随身wifi越来越多了&#xff0c;价格也一直走低&#xff0c;根据我的观察和总结&#xff0c;5g随身wifi可以分为这几档&#xff1a;&#xff08;普遍来说&#xff09; 1&#xff0c;紫光udx710基带芯片&#xff08;也叫v510&#xff09; 代表产品&#xff1a;r106&#x…

Sulfo-CY5 NH2荧光光谱特性-激发波长与发射波长【星戈瑞】

​欢迎来到星戈瑞荧光stargraydye&#xff01;小编带您盘点&#xff1a;Sulfo-CY5 NH2荧光光谱特性-激发波长与发射波长 Sulfo-CY5 NH2是一种荧光染料&#xff0c;其荧光特性使其在生物标记、细胞成像和其他荧光应用中得到诸多应用。荧光是一种发光现象&#xff0c;当Sulfo-CY…

Web容器简介

容器与组件 Java EE&#xff08;Java Platform Enterprise Edition&#xff09;是一种企业级的Java版本。 Java EE是SUN公司提出来的企业版Java开发中间件&#xff0c;它主要用于企业级互联网系统的搭建。 Java EE的本质是一种容器加组件技术&#xff0c;这句话里包含了两个…

1.2亿成都市城市安全风险综合监测预警平台建设项目

导读&#xff1a;原文《1.2亿&#xff01;成都市城市安全风险综合监测预警平台建设项目WORD》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 部分页面&#xff1a; …

牛股预测器V1.0实战(工银瑞信金融科技挑战赛排名第二)

全代码和数据关注公众号《三个篱笆三个班》免费提供&#xff01;一键可跑&#xff0c;每日选股。 对AI炒股感兴趣的小伙伴可加WX群&#xff1a; 赛题概述&#xff1a; 基于人工智能的量化选股投资策略建模挑战 任务描述&#xff1a; 通过数学和计算机技术分析市场数据&…

随身wifi刷超频版Debian系统教程-随身wifi折腾入门

本人折腾Debian的一些记录&#xff0c;有需要的可以参考一下。本文所用到的命令&#xff0c;请先确保已获得root权限执行。 1.更新和升级系统中已安装的软件包 apt-get update && apt-get upgrade &&; apt-get update --fix-missing •apt-get update&#xff…

解锁人工智能项目开发的关键:Python 基础库详解与进阶学习

“ Python 是一种通用的编程语言&#xff0c;广泛用于人工智能项目开发。它有很多可用的库&#xff0c;可以帮助开发人员构建各种人工智能应用程序&#xff0c;如自然语言处理和机器学习。在本文中&#xff0c;我们将介绍一些最流行的 Python 库&#xff0c;以及它们在人工智能…

【机密计算实践】支持 Intel SGX 的 LibOS 项目介绍(二)

续上一篇 【机密计算实践】支持 Intel SGX 的 LibOS 项目介绍(一) 四、Mystikos Mystikos 是一个运行库和一组工具,用于在硬件可信执行环境(TEE)中运行 Linux 应用程序。当前版本支持英特尔 SGX,而未来版本可能支持其他 TEE。 4.1 目标 通过使用硬件 TEE,在…

校园后勤如何实现数字化管理?的修报修系统有哪些产品优势?

数字化时代背景下&#xff0c;校园后勤管理同样需要向智能化方向迈进。随着科技的飞速发展&#xff0c;我们应该充分利用数字化技术和智能化设备来提升校园后勤管理的效率和质量。通过引入可视化、数字化的流程管控系统&#xff0c;我们可以实现在线报修、快速响应处理、全流程…

4G语音胸牌,如何实现在门店接待过程管理的智能化管理?

一个完整的客户销售过程&#xff0c;一般包括线索获取-电话沟通-邀约到店-门店接待-回访-成交。这个过程中我们会发现&#xff0c;销售和客户大量的互动过程都在线下门店&#xff0c;而这个环节是客户最直观感受企业服务和产品的过程&#xff0c;很大程度上直接决定后续的成交与…

精准、高效的RFID资产管理新时代

当谈及高效、精细的企业资产管理时&#xff0c;RFID固定资产管理系统绝对是一项令人振奋的技术。这项引人注目的射频识别技术为企业管理带来了前所未有的革命性变革&#xff0c;以其精准性和高效性赋予了资产管理全新的维度。 想象一下&#xff0c;您无需耗费宝贵的时间和精力…

MinIO线上扩容实战

硬件投入肯定是随着业务的增长而增长&#xff0c;这就要求中间件平台必须提供水平伸缩机制&#xff0c;MinIO对象存储服务也不例外&#xff0c;本文就详细介绍MinIO的扩容。 Minio支持通过增加新的Server Pool来扩容老的集群。每个Server Pool都是一个相对独立的故障域&#x…

安防视频能力平台EasyNVR视频汇聚平台关闭匿名登陆的问题的解决步骤

EasyNVR是基于RTSP/Onvif协议的安防视频能力平台&#xff0c;它可实现设备接入、实时直播、录像、检索与回放、存储、视频分发等视频能力服务&#xff0c;可覆盖全终端平台&#xff08;pc、手机、平板等终端&#xff09;&#xff0c;在智慧工厂、智慧工地、智慧社区、智慧校园等…

YOLOv8教程系列:三、K折交叉验证——让你的每一份标注数据都物尽其用(yolov8目标检测+k折交叉验证法)

YOLOv8教程系列&#xff1a;三、K折交叉验证——让你的每一份标注数据都物尽其用&#xff08;yolov8目标检测k折交叉验证法&#xff09; 0.引言 k折交叉验证&#xff08;K-Fold Cross-Validation&#xff09;是一种在机器学习中常用的模型评估技术&#xff0c;用于估计模型的性…

华星时空展锐芯片5g随身WiFi改串教程

前段时间入手了一个华正易尚&#xff0c;发现插手机卡可以用&#xff0c;插微闯移植卡直接没网&#xff0c;于是研究出展锐改串的教程分享给大家 ⭐注意:理论上所有的展锐芯片棒子都可以用&#xff0c;至于电池机请自行测试 话不多说&#xff0c;教程开始: 1.下载展锐AT改串驱…

服务器数据恢复-EVA存储磁盘故障导致存储崩溃的数据恢复案例

EVA系列存储是一款以虚拟化存储为实现目的的中高端存储设备。EVA存储中的数据在EVA存储设备工作过程中会不断进行迁移&#xff0c;如果运行的任务比较复杂&#xff0c;EVA存储磁盘负载加重&#xff0c;很容易出现故障的。EVA存储通过大量磁盘的冗余空间和故障后rss冗余磁盘动态…

JAVA开发环境接口swagger-ui使用总结

一、前言 swagger-ui是java开发中生产api说明文档的插件&#xff0c;这是后端工程师和前端工程师联调接口的桥梁。生成的文档就减少了很多没必要的沟通提高开发和测试效率。 二、 swagger-ui的使用 1、引入maven依赖 <dependency><groupId>io.springfox</grou…

如何做好流量经营?数字化系统如何加速流量增长

​在用户转化策略上&#xff0c;从“公域流量”到“私域流量”的来源转变&#xff0c;充分说明企业已经意识到公域流量存在成本高、粘度差、稳定性差等问题&#xff0c;开始寻求拥有更低成本、更容易培养忠实度、更容易精准触达的私域流量。但由于企业缺少整体、系统化的私域经…

(AcWing) 900. 整数划分 (计数DP)

一个正整数 n 可以表示成若干个正整数之和&#xff0c;形如&#xff1a;nn1n2…nk&#xff0c;其中 n1≥n2≥…≥nk,k≥1。 我们将这样的一种表示称为正整数 n 的一种划分。 现在给定一个正整数 n&#xff0c;请你求出 n 共有多少种不同的划分方法。 输入格式 共一行&#…

问题:fake_useragent.errors.FakeUserAgentError: Maximum amount of retries reached

问题&#xff1a;fake_useragent.errors.FakeUserAgentError: Maximum amount of retries reached 解决办法&#xff1a; 1. 新建一个文本文档并命名为XXX.json 2. 打开这个文本文档&#xff0c;复制下面内容保存 {"browsers": {"chrome": ["Mozill…