1.Linux线程的概念
1.1什么是线程
在之前我们谈过Linux的进程,每一个进程都有自己的PCB,和自己的进程地址空间。地址空间和物理内存通过页表建立映射。那么现在我要创建一个新的进程,操作系统要给这个新的进程创建pcb,建立自己的地址空间,页表等等。因此创建一个进程的成本是非常大的。那么现在有这样一种方法,要创建一个进程的时候我只创建PCB, 所有的pcb访问的是同一个地址空间。因此这几个进程是共享地址空间的。我们把地址空间的某一区域划分给一个tast_struct,那么这个tast_struct在地址空间中就有了自己的一小份区域。那么我们把这里的一个tast_struct的一个执行流就可以称为线程。
因此总结一下什么是线程呢?
- 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义时:线程是“一个进程内部的控制序列”。
- 一切进程至少都有一个执行线程
- 线程在进程内部运行,本质是进程地址空间内运行
- 在Linux系统中,在CPU眼中,看到的TCBP都要比传统的进程更加轻量化
- 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。
在Linux当中,没有进程和线程的概念上的区分,只有一个叫做执行流!Linux的线程就是用进程PCB模拟的。CPU看到的所有的tast_struct都是一个执行流(线程)。
重要总结:
以前的进程 = 内核数据结构(PCB)+进程对应的代码和数据,而现在我们站在内核视角给进程重新下一个定义。(内核角度)进程是承担分配系统资源的基本实体(进程的基座属性)。因此Linux进程的最大意义不是被执行,而是向系统申请资源的基本单位!而之前我们所写的进程内部就只有一个执行流的进程(单执行流),而今天我们学习了进程内部可以有多个执行流,我们叫做多执行流的进程(多执行流)。
CPU视角下,tast_struct <= 传统的进程PCB. 【等于的时候就是单执行流】
因此,在Linux下,没有真正意义的线程,而是用tast_struct模拟实现的。因此把Linux下的“进程”<= 其他操作系统的进程概念。也可以称之为“轻量级进程”。
线程是调度的基本单位。进程内部有多个执行流,而一个执行流就可以称之为一个线程。一个进程内部可以有多个线程。
1.1.1如何验证一个进程内有多个线程?
Linux下没有直接创建线程的接口,但是有原生的线程库。pthread_create(不是操作系统的接口)。是程序员写出来的库。
pthread_create 线程创建
注意:编译的时候要链接这个库,因此要带 -lpthread 选项,我们可以体现在makefile之中
mythread:mythread.cc g++ -o $@ $^ -pthread -std=c++11.PHONY:cleanclean: rm -f mythreadpthread_join线程等待
线程和进程一样也要等待,因此线程等待的接口时pthread_join。
有了这两个接口,我们就能写出最简单的线程代码,至于函数和代码内部的细节在线程控制会重点强调。
#include <iostream>#include <pthread.h>#include <string>#include <unistd.h>using namespace std;void *callback1(void *args){ string name = (char*)args; while(true) { cout << name << endl; sleep(1); }}void *callback2(void *args){ string name = (char*)args; while(true) { cout << name << endl; sleep(1); }}int main(){ pthread_t tid1; pthread_t tid2; pthread_create(&tid1, nullptr, callback1, (void *)" thread 1"); pthread_create(&tid2, nullptr, callback2, (void *)" thread 2"); while (true) { cout << "我是主线程............" << endl; sleep(1); } pthread_join(tid1, nullptr); pthread_join(tid2, nullptr); return 0;}
我们发现果然是有3个执行流在运行。那么如何证明线程是用进程来模拟的呢?
我们使用ps axj | grep mypthread 命令来查看当前进程的状态
我们发现,我们明明有3个执行流在运行,我们只查到了一个pid为27829进程,因为我们三个执行流是三个线程,属于一个进程,那么我们如何查看线程呢,使用这条命令
ps -aL
我们看到了有3个线程在运行,和我们预期的一样,三个执行流的pid都是27829,这也验证了这三个执行流属于同一个进程,其中第一个线程的LWP(Light weight process -- 轻量级进程编号)也是27829说明是主线程。
因此我们验证了一个进程内部只有多个线程(执行流)。
1.2线程的优点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做得工作要少很多
- 线程占有的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠,线程可以同时等待不同的I/O操作
如果以上线程的有点无法理解,待线程学习完之后回头看便可理解。
1.3线程的缺点
- 性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
同步和调度开销,而可用的资源不变。
- 健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了
不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
- 缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
- 编程难度提高
编写与调试一个多线程程序比单线程程序困难得多
1.4 线程异常
- 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出
1.5 线程用途
- 合理的使用多线程,能提高CPU密集型程序的执行效率
- 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)
2.Linux进程与线程
2.1进程和线程
- 进程时资源分配的基本单位
- 线程是调度的基本单位
- 线程共享进程数据,但也拥有自己的一部分数据(线程ID,一组寄存器,栈,errno,信号屏蔽字,调度优先级)
- 进程的多个线程共享同一地址空间,因此Text Segment(代码段),Data Segment(数据段)都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到。除此之外,各线程还共享以下进程资源和环境(文件描述符表,每个信号的处理方式,当前工作目录,用户id和组id)
2.2 进程和线程的关系
进程和线程的关系如下图:
2.3如何看待之前学习的单进程?
之前学习的单进程是具有一个线程执行流的进程。
(本篇完)