【Linux】从零开始认识多线程 --- 线程概念与底层实现

打印 上一主题 下一主题

主题 1036|帖子 1036|积分 3108


   人间没有单纯的快乐      快乐总是夹带着烦恼和忧愁        人间也没有永远,我们一生坎坷。          -- 杨绛 《我们仨》            

  
1 背景知识

在学习多线程之前,我们先来相识一些背景知识,我们需要这些背景知识来辅助我们明白多线程!
1.1 再谈地址空间

首先,物理内存并不是一连的一整块大空间,物理内存现实上是被划分为很多块(4KB空间),是一个大数组。使用体系进行内存管理不是以字节为单元,而是以内存块为单元,默认大小为4KB!之前文件体系中,体系与磁盘文件进行基础IO的基本单元也是4KB(8个扇区),这都啥颠末经心计划的!
之前我们的可执行程序中会存在地址!可执行程序是储存在磁盘的文件,就会有对应inode,自然的就按4KB储存好了。所以未来进行内存与磁盘的IO交互时,就直接将磁盘的数据块加载到内存块中!

所以一切都是计划好的! 所以IO的基本单元是4KB,无论是磁盘到内存,照旧内存到磁盘都是4KB!而内存中的内存块我们称之为" 页框 / 页帧 "
那使用体系对内存的管理工作就是对4KB空间的管理! 也就是说改变一个变量(假如是int类型 4字节)时,会会直接将整个页框进行写时拷贝,而不是单单一个int类型。这是由于OS体系以为当你修改一个变量时,其四周的变量会有很大概率也发生改变,所以将整个页框进行写时拷贝!从而避免多次写时拷贝(空间换时间)。
4GB的内存中共有 1024 *1024 = 1048576个页框,那么使用体系然后管理这么多的页框呢???
其实每个页框在内核里都有一个struct page结构体来管理,这里面会有该页框的对应属性。那么管理起来就可以通过一个大数组struct page memory[1048576]来管理。所以物理内存也就是这个大数组了!!!
CPU可以通过假造地址转换为物理地址,这是怎么进行的。接下来我们就来谈谈页表,按我们之前的明白页表是假造地址映射物理地址的。那这样储存一个页表就需要2^32个地址映射,这就以及32GB了,所以很显然,使用体系不会以这种方式来储存页表。接下来我们就来学习页表的底层是什么样子的!!!
1.2 页表底层

物理内存中的每个物理地址肯定是有对应的页的,也就是只要找到了对应页就能访问其物理地址。假造地址有2^32个地址:每个地址都是这样的32位序列 0000 0101 0010 0000 0110 1001 1100 1000。那么假造地址是如何转换为物理地址的呢???这个转换不是直接进行转换,而是按照肯定规则进行划分查找:
假造地址共有32位比特位,分为三部分:A部分前10位 ,B部分中间十位,C部分最后12位。

一个地址分为了三部分,并且页表也不止有一张!前10位对应页目录的1024个元素,以A部分作为索引对应每个元素,而这个元素是指向另一张页表的指针。这个页表也有1024个元素,以B部分作为索引,而这个元素是内存中的页框的起始地址(大小为4KB,4096字节),而C部分恰好有4096种组合,作为索引对应每个内存块中的字节!!!C部分中的12位作为页内偏移,与页框的起始地址进行加和,就能找到对应字节!

这样就将假造地址转换为了物理地址!!!
也就看出来:页表的本质就是搜索页框!在通过最后12位来找到对应字节!这样算下来这个页表只花费了页目录 1024 * 4 + 页表 1024 *(1024 * 4) = 4 MB这可比32GB小的太多太多了
但是这样只能找到一个字节啊!一个int都有4个字节,更别说更大的类对象了。这可怎么来找到对应的数据?我们还有“类型”这一关键一步,类型 + 起始地址就能从内存中找到对应的数据!
CPU可以通过MMU帮助我们将假造地址转换为物理地址,页表是储存在CR3寄存器中:
   CR3寄存器通常被称为页目录基址寄存器(Page Directory Base Register)。
它存储了当前任务页目录表(Page Directory Table, PDT)的物理地址。页目录表是一个数据结构,用于在启用分页时转换假造地址到物理地址。
  通过这个寄存器与MMU的硬件电路共同,就可以乐成转换为物理地址!
1.3 明白代码数据划分的本质

地址空间的各个分区是通过限定一批假造地址空间的范围来实现分区。如果我们将代码区的代码拆分为20个函数,让我们的代码来并行运行,这样在技术上可行吗?首先函数也有地址,函数地址是代码的入口地址(函数第一行地址)函数内部每行代码都有地址。一连的代码块就是函数,那么一个函数就对应一批假造地址,如果要拆分函数,就只需要拆分页表就可以了!
所以说:假造地址本质是一种资源,可以进行分配!
只要将假造地址分配清楚,就可以将代码数据进行拆分!
2 线程的概念

先来看官方概念:线程:在进程内部运行,是CPU调度的基本单元。
进程我们很熟悉:是由PCB描述,通过地址空间与页表获取物理内存中的代码与数据。

今天如果我们想要创建一个进程,但不给它分配对应的地址空间,只创建一个task_struct与先前的进程共享地址空间:

线程就是这很多的task_struct,可是这样进程又是什么呢?之前我们学习的是进程 = 内核数据结构 + 进程代码与数据
但是今天,要进行重新矫正。这样多个task_struct不叫进程,叫做进程的执行流!那什么是进程呢?

这一整套是进程!之前我们学习的就是只有单个task_struct的特殊情况!!!
进程从内核来看,是负担分配体系资源的基本实体!
3 澄清与统一线程和进程

在我们这个社会中,家庭是谋划的基本单元,家庭中每个人都有对应的责任:孩子好好学习健康生存,父母勤奋工作,爷爷奶奶安心养老。全部人都在执行自己的事情,但全部的人把自己的事情做好,就能产生将家庭过幸福的结果!
而家庭就是进程,家庭成员就是线程!这就是他们之间的关系!
刚才我们所说的是Linux内核下的线程,对于线程来说,也肯定要和进程一样需要对应使用方法:新建,暂停 ,销毁,调度。那么线程会不会与进程产生关联呢? 接下来我们就来相识线程如何管理。
线程我们一般称为tcb (进程是pcb),那么该结构体struct tcb中就需要:线程id ,优先级,状态,上下文,链接属性…
在Windows下,pcb与tcb是相对独立的,其通过数据结构来关联起来,是两套差别的控制体系!CPU在进行处理时,就要先选择一个进程再选取一个线程,这就需要两个差别的调度算法:

这样就使使用过程非常的复杂!
而Linux罗致Windows的经经验,发现tcb与pcb里面的属性是一致的,并且两个都是执行流,为什么不用一个模块来统一管理呢?!这样就不需要单独计划线程的模块了。
所以Linux是用进程模拟的线程!
我们再来从CPU的角度来看,CPU调用一个task_stuct是小于等于 进程的,进程里面有很多的task_struct!那么CPU需不需要来区分task_stuct是进程照旧线程?当然不需要,执行进程和线程和CPU有什么关系?!你要执行什么就给我CPU什么!给CPU什么执行流(进程或线程),它就执行什么!可以说线程是CPU调度的基本单元。
我们在实践中见一见:

这是创建线程的体系调用,参数有四个:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);

  • pthread_t *thread : 输出型参数,线程id
  • const pthread_attr_t *attr : 线程对应属性,目前设置为nullptr就可以
  • void *(*start_routine) (void ): 这是一个函数指针,函数返回值是void ,函数参数是void
  1. #include <iostream>
  2. #include <pthread.h>
  3. #include <unistd.h>
  4. // 新线程
  5. void *threadStart(void *args)
  6. {
  7.     while (true)
  8.     {
  9.         std::cout << "new thread running..." << std::endl;
  10.         sleep(1);
  11.     }
  12. }
  13. int main()
  14. {
  15.     pthread_t tid;
  16.     pthread_create(&tid, nullptr, threadStart, (void *)"sthread-ew");
  17.     // 主线程
  18.     while (true)
  19.     {
  20.         sleep(1);
  21.         std::cout << "main thread running..." << std::endl;
  22.     }
  23.     return 0;
  24. }
复制代码
我们编译的时间会报一个这样的错误:
(.text+0x1b): undefined reference to main线程未界说,之所以会堕落是由于Linux下使用线程需要引用线程库:

这个库的具体信息我们背面再说。编译的时间链接上动态库pthread就可以了
g++ -o testthread testthread.cc -std=c++11 -lpthread
这样主线程和新线程就可以同时跑了:

我们查看进程信息:

发现只有一个进程:
再来让这两个线程打印一下自己的pid:

会发现,他们两个虽然是两个差别的执行流,但是却是同一个进程!缘故原由不就是这两个进程属于同一个进程内部!我们可以使用ps -aL就可以查看差别线程

这个pid是对应进程的pid,这个LWP其实就是这个线程的id!!!使用体系调度的时间,是通过LWP调度的。CMD是主线程。
有个题目:多进程调度和单进程调度相互影响吗? 进程调度时通过pid来,每个进程都不一样,都有自己的pid,所以并不影响。
下面我们来解决一下几个疑问:

  • 已经有多进程了,为什么还要有多线程??
    创建一个进程需要创建PCB,地址空间,页表,加载代码与数据,创建文件缓冲区等很多使用,但创建一个线程,只需要创建一个PCB,复用本来的地址空间。创建进程的本钱比创建线程高很多切换进程时不仅仅要更换上下文数据,更换地址空间等很多使用,切换线程只需要切换PCB!!!线程删除本钱也很低。但是线程也有缺陷,一个线程堕落(野指针)就是这个进程堕落了,由于他们使用同一个地址空间,所以其他的线程也会报错退出!!! 线程的健壮性很差!而进程是独立的互不影响!进程和线程各有特长!
  • 差别使用体系对线程的实现不一样,那为什么使用体系课本只有一本???
    使用体系是一个指导书,会对使用体系的实现给出一些规定,但是具体的做法并不限制,只有满足规定就可以!
  • 线程的调度为什么本钱更低???
    进程调度会通过CPU一系列寄存器来进行调度,对于CPU来说,多调用几个寄存器应该 不算什么大事,那为什么会本钱更高呢?由于CP中存在一个cache会储存热点数据(进程相关数据) ,要访问数据时,会先在cache中探求,如果命中直接访问,反之进行置换。 所以进程之间切换时,会将cache的数据全部取消操,重新读取,切换线程就不需要进行切换。所以线程的调度本钱更低!!!
  • 线程的本质是代码块!只使用函数的对应代码,即拿页表的一部分来执行!!!
4 总结

4.1 线程的缺点


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


  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要使用体系做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充实使用多处理器的可并行数目
  • 在等候慢速I/O使用结束的同时,程序可执行其他的计算任务
  • 计算密集型应用(进行大量技术,比如加密解密),为了能在多处理器体系上运行,将计算分解到多个线程中实现
  • I/O密集型应用(大量读取写入,下载上传),为了进步性能,将I/O使用重叠。线程可以同时等候差别的I/O使用。
对于多线程和单线程来说,是要合适最好!对于一个2线程的CPU那么创建两个线程是最好的!
4.3 注意

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


  • 文件描述符表
  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL大概自界说的信号处理函数)
  • 当前工作目录
  • 用户id和组id

  • 进程是资源分配的基本单元
  • 线程是调度的基本单元
  • 线程共享进程数据,但也拥有自己的一部分数据:

    • 线程ID
    • 一组寄存器(最重要):硬件上下文数据 — 线程可以动态运行!
    • (最重要):线程中可以处理自己的临时变量,临时变量储存在自己独立的栈区,可以独立完成任务。
    • errno信号屏蔽字
    • 调度优先级


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用户云卷云舒

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表