零、前言
本章主要讲解学习Linux中的线程
一、Linux线程概念
1、什么是线程
- 概念:
在一个程序里的一个执行路线就叫做线程(thread),更准确的定义是:线程是“一个进程内部的控制序列”
一切进程至少都有一个执行线程,也就是主线程,进程由一个或者多个线程组成,即进程中可以有多个执行流
线程是进程的一个执行分支,实在进程内部运行的一个执行流,本质是在进程地址空间内运行,共享进程的进程地址空间,执行进程的一部分代码
以整个运行视角理解:
程序运行,将代码和数据加载到CPU上,同时系统创建对应的进程进行承担分配系统资源,如创建task_struct结构体,构建对应的进程地址空间,页表建立虚拟地址与物理地址的映射等等,即进程是承担分配系统资源的基本单元
在进程中可能存在多个执行流(一定有个主执行流),也就是线程,而这些执行流都是由task_struct描述的,共享同一个进行地址空间,透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流,执行程序的部分代码,这些执行流可以进行并发执行,由于是在进行内部运行,不用切换整个进程的上下文数据,只需切换线程的上下文数据,即线程是系统调度的基本单元
示图:
注:在Linux系统下的CPU眼中,看到的PCB(task_struct)都要比传统的进程更加轻量化
如何理解之前所说的’进程’:
进程是一个大的整体,包括task_struct(PCB),进程地址空间、文件、信号等,是承担分配系统资源的基本实体,而之前所受的进程都只有一个task_struct,也就是该进程内部只有一个执行流
注意:
在Linux中,CPU只关心一个一个的独立执行流,无论进程内部只有一个执行流还是有多个执行流,CPU都是以task_struct为单位进行调度的
Linux下并不存在真正的多线程,而是用进程模拟的。如果要支持真的线程(TCB)会提高操作系统的复杂程度。而线程的和进程的控制块基本是类似实现的,因此Linux直接复用了进程控制块,所以Linux中的所有执行流都叫做轻量级进程
在Linux中都没有真正意义的线程,所以也就没有真正意义上的线程相关的系统调用,但是Linux提供了轻量级进程相关的库和接口,例如vfork函数和原生线程库pthread
2、vfork函数/pthread线程库
- vfork函数原型:
pid_t vfork(void);
- 注意:
- 功能:创建子进程,但是父子共享进程地址空间
- 返回值:成功给父进程返回子进程的PID;给子进程返回0
示例:
#include<stdio.h> #include<unistd.h> #include<sys/types.h> int main() { int val=100; pid_t id=vfork(); if(id==0) { //child int cnt=0; while(1) { printf("i am child pid:%d ppid:%d val:%d &val:%p\n",getpid(),getppid(),val,&val); cnt++; sleep(1); if(cnt==2) val=200; if(cnt==5) exit(0); } } else if(id>0) { //father int cnt=0; while(1) { printf("i am father pid:%d ppid:%d val:%d &val:%p\n",getpid(),getppid(),val,&val); cnt++; sleep(1); if(cnt==3) val=300; } } return 0; }
注:vfork() 保证子进程先运行,在它调用 exec(进程替换) 或 exit(退出进程)之后父进程才可能被调度运行;如果子进程没有调用 exec, exit, 程序则会导致死锁,程序是有问题的程序,没有意义
原生线程库pthread:
在Linux中,站在内核角度没有真正意义上线程相关的接口,但是站在用户角度,当用户想创建一个线程时更期望使用thread_create这样类似的接口,因此系统为用户层提供了原生线程库pthread
原生线程库实际就是对轻量级进程的系统调用进行了封装,在用户层模拟实现了一套线程相关的接口
3、线程优缺点及其他分析
线程的优点:
创建一个新线程的代价要比创建一个新进程小得多
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多线程占用的资源要比进程少很多
能充分利用多处理器的可并行数量
在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作(如边下视频边看视频)
注意:
计算密集型:执行流的大部分任务,主要以计算为主。比如加密解密、大数据查找等
IO密集型:执行流的大部分任务,主要以IO为主。比如刷磁盘、访问数据库、访问网络等
线程的缺点:
性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变
健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了;不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺保护的
缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响
编程难度提高:编写与调试一个多线程程序比单线程程序困难得多
线程异常:
单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出
线程用途:
合理的使用多线程,能提高CPU密集型程序的执行效率
合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)