文章目录
1.线程的基本特性
1.1 线程的基本特性
- 引入目的:为了减少程序在并发执行时所付出的时空开销,使得OS具有更好的并发性。
- 和进程的关系:每个进程中都有唯一的主线程,且只能有一个,主线程和进程是相互依存的关系,主线程结束进程也会结束。
1.2 线程的优缺点
1.2.1 线程的优点
- CPU调度的基本单位,切换非常迅速,开销小。
- 一个进程同时存在多个线程。
- 各个线程之间可以并发执行和共享资源。
1.2.2 线程的缺点
- 一个线程崩溃,可能导致所属进程的所有线程崩溃。
2.线程的分类
2.1 用户级线程(ULT)
2.1.1 用户级线程定义
- 不需要操作系统的干预,由用户级的线程库函数完成线程的管理,包括进程的创建、终止、同步和调度。
2.1.2 用户级线程优点
- 线程切换不需要切换到内核空间,通过线程库函数即可完成,所以速度快。
- 用户级线程的实现与OS平台无关。
- 允许每个进程自定义线程的调度算法。
2.1.3 用户级线程缺点
- 当线程发生阻塞时,不仅仅该线程被阻塞,而且进程的所有线程也被阻塞。
- 除非主动交出CPU使用权,否则其他线程无法继续执行。
- 由于时间片分配给进程,因此每个线程得到的时间片较少,执行速度慢。
2.2 内核级线程(KST)
2.2.1 内核级线程定义
- 由操作系统内核完成线程的创建、终止和管理,内核级线程必须在内核态才能完成。
2.2.2 内核级线程优点
- 当发生线程阻塞时,内核可以调度其它进程占用CPU,也可以运行其它进程的线程。
- 由于时间片分配给线程,因此每个线程得到的时间片较多,执行速度快。
2.2.3 内核级线程缺点
- 线程的创建、终止和切换都是由内核来完成,因此系统开销较大。
2.3 轻量级线程(内核级支持的用户级线程)
2.3.1 多对一模型
- 定义:多个用户级线程映射到一个内核级线程。
- 优点:线程管理开销小,效率高,不需要切换到内核态。
- 缺点:如果一个线程阻塞,整个进程都阻塞。
2.3.2 一对一模型
- 定义:一个用户级线程映射到一个内核级线程。
- 优点:如果有一个线程阻塞,其它线程可以继续执行,并发能力强。
- 缺点:每次创建一个用户级线程相应的需要创建内核级线程,开销大。
2.3.3 多对多模型
- 定义:多个用户级线程映射到多个内核级线程。
- 优点:集合二者优点
3.线程上下文切换
3.1 引起线程上下文切换的因素
- 当前执行线程的时间片用完之后,系统CPU正常调度下一个任务。
- 在中断处理中,其他程序打断当前正在运行的程序。例如当CPU接收到中断请求时,会在正在运行的程序和发起中断请求的程序之间进行一次上下文切换。
- 对于一些操作系统,当进行用户态切换时也会进行一次上下文切换,虽然这不是必须的。
- 多个任务抢占锁资源,在多任务处理中,CPU会在不同程序之间来回切换,每个程序都有相应的处理时间片,CPU在两个时间片的间隔中进行上下文切换。
3.2 线程上下文切换优化手段
- 无锁并发编程:多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash取模分段,不同的线程处理不同段的数据。
- 避免创建不需要的线程。
- 使用协程: 协程能在单线程里实现多任务调度,并在单线程里维持多个任务间的切换。
3.3 线程和进程上下文切换开销比较
- 切换虚拟地址空间,切换内核栈和硬件上下文,CPU高速缓存失效、页表切换,开销很大。
- 切换时只需保存和设置少量寄存器内容,因此开销很小。
4.线程安全
4.1 线程安全定义
- 当多个线程访问某个方法时,不管通过怎样的调用方式或者说这些线程如何交替地执行,在主程序中不需要去做任何的同步,这个类的结果行为都是设想的正确行为,那么我们就可以说这个类是线程安全的。
4.2 如何保证线程安全
- 对象的生死不能由对象自身拥有的
mutex(互斥器)
来保护; - 如何避免对象析构时可能存在的race conditon(竞态条件)是C++多线程编程面临的基本问题, C++借用shared_ptr和weak_ptr完美解决;
shared_ptr和weak_ptr
是实现线程安全的Observer设计模式的必备技术;
4.3 可重入函数
4.3.1 可重入函数的定义
- 可重入函数是线程安全函数的一种,其特点在于它们被多个线程调用时,不会引用任何共享数据。函数是可重入的,是指对于相同的(并且合法的)函数参数(包括无参函数的情况),多次调用此函数产生的行为是可预期的,即函数的行为一致,或者结果相同。不能保证这一点的函数称为不可重入函数。
4.3.2 显式可重入函数
- 如果所有函数的参数都是传值传递的(没有指针),并且所有的数据引用都是本地的栈变量(也就是说没有引用静态或全局变量),那么函数就是显式可重入的。也就是说不管如何调用,我们都可断言它是可重入的。
4.3.3 隐式可重入函数
- 可重入函数中的一些参数是引用传递(使用指针)。也就是说,在调用线程小心地传递指向非共享数据的指针时,它才是可重入的。
4.4 线程安全和函数可重入的区别
- 可重入和线程安全是两个不同的概念。可重入函数一定是线程安全的,而线程安全的函数可能是重入的,也可能是不重入的,线程不安全的函数一定是不可重入的。可重入函数要解决的问题是,不在函数内部使用静态或全局数据和不返回静态或全局数据,也不调用不可重入函数。
5.线程独占资源和共享资源
5.1 共享资源
- 进程ID和目录。
- 进程申请的堆内存。
- 进程打开的文件描述符。
- 进程的全局变量(可用于线程之间通信)和静态变量。
- 信号处理器。
5.2 独占资源
- 线程ID:同一进程中每个线程拥有唯一的线程ID。
- 线程堆栈:线程可以进行函数调用,必然会使用函数堆栈。
- 线程优先级:由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参数,这个参数就是线程的优先级。
- 信号屏蔽码:由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自己管理。但所有的线程都共享同样的信号处理器。
- 错误返回码:线程执行出错时,必须明确是哪个线程出现何种错误,因此不同的线程应该拥有自己的错误返回码变量。
- 寄存器组的值:由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线程切换到另一个线程时,必须将原有的线程的寄存器集合的状态保存,以便将来该线程在被重新切换到时能得以恢复。
- 另外一种回答是包括线程栈、寄存器数据和PC。
5.3 线程创建的时候线程池的线程个数如何确定
- 线程池中线程的数目是跟线程池所要处理的任务性质有关,性质不同的任务可以交给不同规模的线程池执行。
任务的性质:CPU密集型任务、IO密集型任务、混合型任务。
任务的优先级:高、中、低。
任务的执行时间:长、中、短。
任务的依赖性:是否依赖其他系统资源,如数据库连接等。
6.多线程
6.1 多线程背景
- 在一个文件内的多个函数通常都是按照main函数中出现的顺序来执行,但是在分时系统下,我们可以让每个函数都作为一个逻辑流并发执行,最简单的方式就是采用多线程策略。在main函数中调用多线程接口创建线程,每个线程对应特定的函数,这样就可以不按照main函数中各个函数出现的顺