程序员的自我修养——线程基础

什么是线程:有时也被成为轻量级进程,是程序执行流的最小单元。

一个标准的线程是由线程ID,当前指令指针(PC)、寄存器集合和堆栈组成。

一个进程是由一个到多个线程组成,各个线程之间共享程序的内存空间(包括代码段,数据段的堆等)及一些进程级的资源(如打开文件和信号)

多个线程可以互不干扰的并发执行,并共享进程的全局变量和堆的数据;

线程的访问权限

线程的访问权限非常自由,可以访问进程内存里的所有数据;

线程调度与优先级

单处理器对应多线程:操作系统让这些多线程轮流执行,每次仅执行一小段时间(通常是几十秒),这样每个线程“看起来”就像是在同时执行;这样的一个处理器上不断切换线程的操作成为“线程调度”

 

线程调度中的三种状态:

1):运行,此时线程正在运行

2):就绪,此时线程可以立即运行,但CPU已被占用

3):等待,此时线程正在等待某一事件发生,无法执行

 

每当一个程序离开运行状态,调度系统就会选择一个就绪的线程运行;一个在等待状态的线程事件发生以后进入就绪状态。

 

在优先级调度的情况下,线程优先级的改变一般有三种方式:

用户指定优先级;

根据进入等待状态的频繁程度提升或降低优先级;

长时间得不到执行而被提升优先级;

 

Linux的多线程

在Linux下,可以用以下三个方法创建一个新的任务:

(1)fork:复制当前进程

(2)exec:使用新的可执行映像覆盖当前可执行映像

(3)clone:创建子进程,并从指定位置开始执行;

 

fork:

pid_t pid;

if(pid==fork()){…}

在fork调用以后,新的任务启动并和本任务一起从fork函数返回,但不同的是本任务的fork将返回新任务的pid,而新任务的fork将返回0;

 

fork产生新任务的速度非常快,因为fork不复制原任务的内存空间,而是和原任务一起共享一个写时拷贝的内存空间;

 

所谓写时拷贝:指的是两个任务可以同时自由地读取内存,但任意一个任务试图对内存进行修改时,内存就会复制一份提供给修改方单独使用,以免影响到其他的任务使用;

 

fork只能够产生本任务的镜像,因此必须使用exec配合才能启动别的新任务,exec可以用新的可执行映像替代当前的可执行映像,因此在fork产生了一个新任务之后,新任务可以调用exec来执行新的可执行文件;

 

头文件都定义在pthread.h中

创建一个线程:#include<pthread.h>

int pthread_create (pthread_t *thread,const pthread_attr_t* attr,void*(*start_routine)(void*),void*arg)

 

thread参数是新线程的标识符,后续的pthread_*函数通过它来引用新线程;

attr参数用于设置新线程的属性,给它传递NULL表示使用默认线程属性;

Start_routine 和arg参数分别制定新线程将运行的函数及参数;

pthread_create成功时返回0,失败时返回错误码;

 

 

结束一个线程:void pthread_exit(void *retval)

函数通过retval参数向线程的回收者传递其退出信息;

 

线程安全:

多线程程序处于一个多变的环境当中,可访问的全局变量和堆数据都可能随时被其他的线程改变;

手段:同步与锁

 

原子的:单指令操作称为原子的,无论如何,单条指令的执行是不会被打断的;

为了避免多个线程同时读写一个数据而产生不可预料的后果,我们要将各个线程对同一个数据的访问同步(所谓同步,就是在一个线程访问数据未结束时,其他线程不得对同一个数据进行访问),因此,对数据的访问被原子化;

 

同步的常见方法:(信号量、互斥量、临界区,读写锁、条件变量)

锁:每一个线程在访问数据或资源时首先试图获取锁,并在访问结束后释放锁;

信号量:线程访问资源的时候首先获取信号量;

操作如下:

(1)将信号量的值减1;

(2)如果信号量的值小于0,则进入等待状态;否则继续执行;

访问完资源之后,线程释放信号量;

(3)将信号量的值加1;

(4)如果信号量的值小于1,唤醒一个等待中的线程;

 

互斥量&&信号量

同:资源仅同时允许一个线程访问;

异:信号量在整个系统中,可以被任意线程获取并释放,也就是说,同一个信号量可以被系统中的一个线程获取后,另一个线程释放;

而互斥量要求哪个线程获取了互斥量,哪个线程就要负责释放这个锁,其他线程越俎代庖去释放互斥量是无效的;

 

临界区:是比互斥量更加严格的同步手段

把临界区的锁的获取称为进入临界区;

而把锁的释放称为离开临界区。

 

区别(与信号量和互斥量)

互斥量和信号量在系统的任何进程里都是可见的;

也就是说一个进程创建了一个互斥量或信号量,另一个进程试图去获取该锁是合法的;

临界区的作用范围仅限于本进程,其他的进程无法获取该锁

 

读写锁:

两种方式:共享的  独占的

当锁出于自由状态时,试图以任何一种方式获取锁都能成功,并将锁置于对应的状态;(如上图)

 

条件变量:作为同步的一种手段,作用类似于一个栅栏;

对于条件变量,线程有两种操作:

(1)首先线程可以等待条件变量,一个条件变量可以被多个线程等待;

(2)线程可以唤醒条件变量,此时某个或所有等待此条件变量的线程都会被唤醒并继续支持;

(也就是说,使用条件变量可以让许多现车鞥一起等待某个事件的发生,当事件发生时,所有线程可以一起恢复执行)。

猜你喜欢

转载自blog.csdn.net/weixin_41298329/article/details/81430086