目录:
线程
线程概念
进程与线程的关系
pthread_create是一个库函数运行在用户态,创建的是用户级线程,在内核中通过轻量级进程实现调度
-
在创建进程时系统会给进程分配各种资源,还需要创建PCB和页表,虚拟地址,还要把文件,父进程,子进程之间的关系组织好,还要再物理空间开辟内存,把代码和数据加载到内存中,把物理空间和虚拟地址通过页表连接起来建立映射关系,这样一个进程就创建好了。要是还想创建一个进程还需要以上步骤。
-
这样我们可以创建多个PCB指向同一块地址空间,把同一份资源分成多份,分配给PCB,让他们执行这个程序的一部分,这样就形成了一个进程多个控制块(PCB)。
进程:承担系统资源分配的基本实体。
线程:调度的基本单位,是进程的内部的一条执行流。(线程再进程地址空间内运行)
线程:进程=n:1
Linux中没有真正意义上的线程,线程适用进程模拟的:轻量级进程。
线程并没有独立的虚拟地址空间,只是在进程虚拟地址空间中拥有相对独立的一块空间
-
. 在一个程序里的一个执行路线就叫做线程(thread)更准确的定义是:线程是“一个进程内部的控制序列”
-
. 一切进程至少都有一个执行线程线程在进程内部运行,本质是在进程地址空间内运行
-
在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
线程优点
- . 创建一个新线程的代价要比创建一个新进程小得多
- . 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- . 线程占用的资源要比进程少很多
- . 能充分利用多处理器的可并行数量
在等待慢速I/O操作结束的同时,程序可执行其他的计算任务 - . 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- . I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程的缺点
- 性能损失
- 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
同步和调度开销,而可用的资源不变。 - 健壮性降低
- 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了
不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。 - 缺乏访问控制
- 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
- 编程难度提高
- 编写与调试一个多线程程序比单线程程序困难得多
线程用途
- 合理的使用多线程,能提高CPU密集型程序的执行效率
- 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现)
什么是计算密集型
- 计算密集型就是计算、逻辑判断量非常大而且集中的类型,因为主要占用cpu资源所以又叫cpu密集型,而且当计算任务数等于cpu核心数的时候,是cpu运行效率最高的时候。
特点:消耗cpu
什么是IO密集型
-
IO密集型就是磁盘的读取数据和输出数据非常大的时候就是属于IO密集型
由于IO操作的运行时间远远大于cpu、内存运行时间,所以任务的大部分时间都是在等待IO操作完成,IO的特点是cpu消耗小,所以,IO任务越多,cpu效率越高,当然不是越多越好,有一个极限值。 -
IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。
cpu核心数
- cpu核心数是指一个cpu由几个核心组成,核心数越多,cpu运行速度越快,比如处理同一份数据,单核是指一个人处理,双核是指两个人处理,所以核心数越多,cpu性能越好。
线程异常
- 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出
Linux进程VS线程
进程和线程
- 进程是资源分配的基本单位
- 线程是调度的基本单位(在linux中用lwp调度)
- 线程共享进程数据,但也拥有自己的一部分数据:
线程ID(用户看到的thread_t,内核看到的lwp)
一组寄存器(线程的硬件上下文,也称tss)
线程私有栈
errno
信号屏蔽字
调度优先级
进程的多线程共享
- 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
文件描述符表
每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
当前工作目录
用户id和组id
线程不是绝对共享,因为他还有私有部分数据。进程不一定绝对独立,因为还有进程间通信。
linux线程控制
POSIX线程库
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
- 要使用这些函数库,要通过引入头文<pthread.h>链接这些线程函数库时要使用编译器命令的“-lpthread”选项
创建进程
功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(start_routine)(void), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
错误检查:
- 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
- pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)。而是将错误码通过返回值返回
- pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要比读取线程内的errno变量的开销更小
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<pthread.h>
4 void *thread_hanshu(void *args){
5 while(1){
6 printf("i an %s,pid:%d\n",(char*)args,getpid());
7 sleep(1);
8
9
10 }
11 }
12 int main(){
13 pthread_t c;
14 pthread_create(&c,NULL,thread_hanshu,(void*)"pthread 1");
15 while(1){
16 printf("i am main pthread,pid:%d\n",getpid());
17 sleep(2);
18 }
19
20
21 }
-
每在创建一个进程,都要在内核区创建一个执行流
-
任何线程异常都会导致进程的退出
-
这里看到两个线程的pid号相同,属于同一个进程而lwp不同属于两个进程
线程ID及进程地址空间布局
- pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
- 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
- pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
- 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID:
- pthread_t pthread_self(void);
- pthread_t 到底是什么类型呢?取决于实现。对于Linux目前实现的NPTL实现而言,pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。
- 描述线程是同一个结构体,在动态库中线程信息以数组的结构形式组织在动态库中,而线程pid(lwp)就是对应线程控制块在库的起始地址。