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

线程Ⅰ

  • 一、概念
    • 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

简单计算器的实现(含转移表实现)

文章目录 计算器的一般实现使⽤函数指针数组的实现&#xff08;转移表&#xff09; 计算器的一般实现 通过函数的调用&#xff0c;实现加减乘除 # define _CRT_SECURE_NO_WARNINGS#include<stdio.h>int Add(int x, int y) {return x y; }int Sub(int x, int y) {retur…

变频器和plc之间无线MODBUS通讯

在工业现场由PLC远程控制变频器的应用非常常见&#xff0c;如果挖沟布线不便或者变频器在移动设备上&#xff0c;那么采用无线通讯就是最佳方案。 这里我们选用最常用的三菱 FX2N PLC和三菱变频器为例&#xff0c;并结合日系plc专用无线通讯终端DTD435M来说明PLC与变频器之间的…

# Lua与C++交互(二)———— 交互

C 调用lua 基础调用 再来温习一下 myName “beauty girl” C想要获取myName的值&#xff0c;根据规则&#xff0c;它需要把myName压入栈中&#xff0c;这样lua就能看到&#xff1b;lua从堆栈中获取myName的值&#xff0c;此时栈顶为空&#xff1b;lua拿着myName去全局表中查…

C++进阶 多线程相关

本篇博客介绍&#xff1a; 主要介绍C中的一些线程操作以及线程库 C进阶 多线程相关 为什么要有线程库线程库介绍线程库常见的接口构造线程对象获取线程idjoin和deteach mutex库原子操作相关条件变量库总结 为什么要有线程库 我们在Linux中写多线程的时候使用的是Linux下提供的…

怎样做好数字营销呢?

2023 年&#xff0c;数字营销将随着新技术、趋势和消费者行为的不断发展而不断发展。要在 2023 年在数字营销领域取得成功&#xff0c;请考虑以下策略&#xff1a; 1.内容质量和个性化&#xff1a; 专注于制作与目标受众产生共鸣的高质量且相关的内容。 根据用户偏好、行为和…

【健康医疗】Axure用药提醒小程序原型图,健康管理用药助手原型模板

作品概况 页面数量&#xff1a;共 20 页 兼容软件&#xff1a;Axure RP 9/10&#xff0c;不支持低版本 应用领域&#xff1a;健康管理&#xff0c;用药助手 作品申明&#xff1a;页面内容仅用于功能演示&#xff0c;无实际功能 作品特色 本作品为「用药提醒」小程序原型图…

【VS】InstallerProjects.vsix下载 Microsoft Visual Studio Installer Projects (2022)

InstallerProjects.vsix 是微软官方提供的winform程序打包工具&#xff0c;但是国内下载安装有时候比较慢。虽然只有5m左右&#xff0c;但是国内就是下载不下来。现将官网地址和下载后的百度网盘共享地址展示如下&#xff1a;方便大家使用 官方地址&#xff1a;https://market…

[C++] string类常用接口的模拟实现

文章目录 1、前言2、遍历2.1 operator[ ]下标方式2.2 迭代器2.3 范围for2.4 c_str 3、容量相关3.1 size&#xff08;大小&#xff09;3.2 capacity&#xff08;容量&#xff09;3.3 empty&#xff08;判空&#xff09;3.4 clear&#xff08;清理&#xff09;3.5 reserve3.6 res…

生成模型 -- GAN

文章目录 1. 生成模型与判别模型1.1 生成模型 2. VAE3. GAN3.1 GAN-生成对抗网络3.2 GAN-生成对抗网络的训练3.2.1 判别模型的训练&#xff1a;3.2.2 生成网络的训练&#xff1a; 4. LeakyReLU5. GAN代码实例 1. 生成模型与判别模型 生成模型与判别模型 我们前面几章主要介绍了…

UE4 材质学习笔记

CheapContrast与CheapContrast_RGB都是提升对比度的&#xff0c;一个是一维输入&#xff0c;一个是三维输入&#xff0c;让亮的地方更亮&#xff0c;暗的地方更暗&#xff0c;不像power虽然也是提升对比度&#xff0c;但是使用过后的结果都是变暗或者最多不变&#xff08;值为1…

Mybatis简单入门

星光下的赶路人star的个人主页 夏天就是吹拂着不可预期的风 文章目录 1、Mybatis介绍1.1 JDBC痛点1.2 程序员的诉求1.3 Mybatis简介 2、数据准备2.1 数据准备2.2 建工程2.3 Employee类2.4 Mybatis的全局配置2.5 编写要执行的SQL2.6 编写java程序2.7 稍微总结一下流程 3、解决属…

什么是安全测试报告,怎么获得软件安全检测报告?

安全测试报告 软件安全测试报告&#xff1a;是指测试人员对软件产品的安全缺陷和非法入侵防范能力进行检查和验证的过程&#xff0c;并对软件安全质量进行整体评估&#xff0c;发现软件的缺陷与 bug&#xff0c;为开发人员修复漏洞、提高软件质量奠定坚实的基础。 怎么获得靠谱…

Hadoop学习一(初识大数据)

目录 一 什么是大数据&#xff1f; 二 大数据特征 三 分布式计算 四 Hadoop是什么? 五 Hadoop发展及版本 六 为什么要使用Hadoop 七 Hadoop vs. RDBMS 八 Hadoop生态圈 九 Hadoop架构 一 什么是大数据&#xff1f; 大数据是指无法在一定时间内用常规软件工具对其内…

昌硕科技、世硕电子同步上线法大大电子合同

近日&#xff0c;世界500强企业和硕联合旗下上海昌硕科技有限公司&#xff08;以下简称“昌硕科技”&#xff09;、世硕电子&#xff08;昆山&#xff09;有限公司&#xff08;以下简称“世硕电子”&#xff09;的电子签项目正式上线。上线仪式在上海浦东和硕集团科研大楼举行&…

渗透测试方法论

文章目录 渗透测试方法论1. 渗透测试种类黑盒测试白盒测试脆弱性评估 2. 安全测试方法论2.1 OWASP TOP 102.3 CWE2.4 CVE 3. 渗透测试流程3.1 通用渗透测试框架3.1.1 范围界定3.1.2 信息搜集3.1.3 目标识别3.1.4 服务枚举3.1.5 漏洞映射3.1.6 社会工程学3.1.7 漏洞利用3.1.8 权…

Java课题笔记~ SpringBoot基础配置

二、基础配置 1. 配置文件格式 问题导入 框架常见的配置文件有哪几种形式&#xff1f; 1.1 修改服务器端口 http://localhost:8080/books/1 >>> http://localhost/books/1 SpringBoot提供了多种属性配置方式 application.properties server.port80 applicati…

jmeter HTTP请求默认值

首先&#xff0c;打开JMeter并创建一个新的测试计划。 右键单击测试计划&#xff0c;选择"添加" > “配置元件” > “HTTP请求默认值”。 在HTTP请求默认值中&#xff0c;您可以设置全局的HTTP请求属性&#xff0c;例如&#xff1a; 服务器地址&#xff1a…

神经网络简单理解:机场登机

目录 神经网络简单理解&#xff1a;机场登机 ​编辑 激活函数&#xff1a;转为非线性问题 ​编辑 激活函数ReLU 通过神经元升维&#xff08;神经元数量&#xff09;&#xff1a;提升线性转化能力 通过增加隐藏层&#xff1a;增加非线性转化能力​编辑 模型越大&#xff0c;…

uniapp日期选择组件优化

<uni-forms-item label="出生年月" name="birthDate"><view style="display: flex;flex-direction: row;align-items: center;height: 100%;"><view class="" v-

【图论】最短路的传送问题

一.分层图问题&#xff08;单源传送&#xff09; &#xff08;1&#xff09;题目 P4568 [JLOI2011] 飞行路线 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) &#xff08;2&#xff09;思路 可知背景就是求最短路问题&#xff0c;但难点是可以使一条路距离缩短至0&#xf…