第四章:线程概述【单线程、多线程模型、线程库、线程池】

目录

4.1 概述

4.1.1 单线程进程和多线程进程

4.1.2 多线程编程的优点

4.2 多线程模型

4.2.1 多对一模型

4.2.2 一对一模型

4.2.3 多对多模型

4.2.4 双层模型(二级模型)

4.3 线程库

4.4 隐式多线程

4.4.1 线程池

4.4.2 OpenMP

4.4.3 大中央调度 GCD

4.5 多线程问题

4.5.1 系统调用fork()和exec()

4.5.2 信号处理

4.5.3 线程撤销

4.5.4 线程本地存储


4.1 概述

线程是CPU使用的基本单元,由线程ID程序计数器寄存器组堆栈组成。它与属于同一进程的其他线程共享代码段,数据段和其他操作系统资源。

4.1.1 单线程进程和多线程进程

4.1.2 多线程编程的优点

  1. 响应度高:一个多线程的程序即使部分阻塞,其他部分仍能运行,从而增加了对用户的响应程度。
  2. 资源共享:线程默认共享他们的所属进程的内存和资源。
  3. 经济:创建和切换线程比创建进程更节省资源和时间。
  4. 多处理器体系结构的利用:多线程能充分利用多处理器体系。

 

4.2 多线程模型

有两种方法提供线程支持:用户线程和内核线程

  • 用户线程受内核支持,无须内核管理
  • 内核线程由操作系统支持和管理

在用户线程和内核线程之间存在一定的关系,即多线程模型,以下讨论三种常用的关系:多对一,一对一,多对多。

4.2.1 多对一模型

多个用户线程映射到一个内核线程

  • 优点:线程管理由线程库在用户空间完成,效率比较高
  • 缺点:如果一个线程阻塞,整个进程就会阻塞;且多个线程无法并行运行在多处理器上

4.2.2 一对一模型

每个用户线程映射到一个内核线程上

  • 优点:比多对一模型更好的并发功能;一个线程阻塞时,其他线程能够继续调用;多个线程能够并发运行在多处理器
  • 缺点:创建内核线程的开销会影响应用程序的功能,系统限制了支持的线程数量。

4.2.3 多对多模型

  • 多对一模型可以创建任意多的用户线程,但是没有增加并发性
  • 一对一模型增强了并发性,但开发者要小心不能在应用程序中创建太多的进程

多对多模型没有上述的所有缺点,它多路复用了许多用户线程到同样数量或更小数量的内核线程上

4.2.4 双层模型(二级模型)

同时支持多对多和一对一。

 

4.3 线程库

线程库为程序员提供创建和管理线程的API。

实现线程库的方法有两种:

  1. 在用户空间中提供一个没有内核支持的库。库内的所有代码和数据结构都位于用户空间,调用库内的函数只会导致用户空间的本地函数调用,而不是系统调用。
  2. 实现由操作系统直接支持的内核级的一个库。库内的所有代码和数据结构都位于内核空间,调用库内的函数会导致对内核的系统调用。

目前三种主要的线程库:POSIX Pthreads、Windows、Java。

 

4.4 隐式多线程

将多线程的创建和管理交给编译器和运行时库来完成,这种策略称为隐式多线程。

探讨如下三种设计方法。

4.4.1 线程池

多线程服务器有一些潜在问题:第一个是关于处理请求之前用以创建线程的时间,以及线程在完成工作之后就要被丢弃这一事实。第二个,如果允许所有并发请求都通过新线程来处理,那么将没法限制在系统中并发执行的线程的数量。无限制的线程会耗尽系统资源。解决这一问题是使用线程池

线程池的思想:在进程开始时创建一定数量的线程,并放入到池中以等待工作。当服务器收到请求时,他会唤醒池中的一个线程,并将需要服务的请求传递给他,一旦线程完成了服务,它会返回到池中再等待工作。如果池中没有可用的线程,那么服务器会一直等待,直到有空线程为止。

线程池的优点:

  • 用现有线程处理请求要比等待创建新的线程要快
  • 线程池限制了在任何时候可用线程的数量。
  • 将执行任务和创建任务分离,允许我们采用不同的运行策略。例如延时执行、定期执行。

线程池中的线程数量由系统CPU的数量、物理内存的大小和并发客户请求的期望值等因素决定。比较高级的线程池能动态的调整线程的数量,以适应具体情况。

4.4.2 OpenMP

OpenMp为一组编译指令和API,用于编写C,C++,FORTRAN等语言的程序,支持共享内存环境下的并行编程。

OpenMP识别并行区间,即可并行运行的代码块。编译器根据程序中添加的pragma指令,自动将程序并行处理,使用OpenMP降低了并行编程的难度和复杂度。

4.4.3 大中央调度 GCD

大中央调度是Apple Mac OS X和 iOS 操作系统的一种技术,为C,C++,Swift,API,运行库的扩展,它允许应用开发人员将某些代码区段并行运行。

GCD提供调度队列来处理提交的任务:管理向GCD提交的任务并且以先进先出的顺序来执行任务

  • 顺序调度队列:顺序队列中的任务同一时间只执行一件
  • 并发调度队列:并发队伍中的多个任务可以并行执行

 

4.5 多线程问题

4.5.1 系统调用fork()和exec()

在多线程程序中,系统调用fork()和exec()的语义有所改变。

如果程序中一个进程调用fork(),那么新进程会复制所有线程,还是新进程只有单个线程?

有的UNIX系统有两种形式的fork(),一种复制所有线程,另一种只复制调用了系统调用fork()的线程。

exec()工作方式基本不变,如果一个线程调用系统调用exec(),那么exec()参数所指定的程序会替换整个进程,包括所有线程。

如果调用fork()之后立即调用exec(),那么没有必要复制所有线程,因为exec()参数所指定的程序会替换整个进程。在这种情况下,只复制调用线程比较适当。
不过,如果在fork()之后另一进程并不调用exec(),那么另一进程就应复制所有进程。

4.5.2 信号处理

信号在Unix中用来通知进程某个特定事件已经发生,信号可以同步或异步接收。

所有有信号具有同样的模式:

  1. 信号由特定事件的发生所产生
  2. 产生的信号要发送到某个进程
  3. 信号一旦受到就应处理。

同步信号:例如访问非法内存或被0除。在这种情况下,如果运行程序执行这些动作,那么就产生信号,同步信号发送到执行操作而产生信号的同一进程(同步的原因)。
异步信号:当一个信号由运行进程之外的事件产生,那么进程就异步接收这一信号。如使用特殊键(Ctrl + C)或者定时器到期。通常,异步信号被发送到另一个进程。

信号的处理程序有两种:

  1. 缺省的信号处理程序。
  2. 用户定义的信号处理程序。

每个信号都有一个缺省信号处理程序(默认信号处理程序),当处理信号时由内核来运行,这种默认动作可以通过用户定义的信号处理程序来改写。信号可以按照不同的方式处理。有的信号可以简单的忽略(如改变窗口大小),有的需要终止程序来处理(非法内存访问)。

单线程程序的信号处理比较直接,信号总是发送给进程

当多线程时,信号传递到哪里去?

  1. 信号到信号所适用的线程
  2. 信号到进程内的每个线程
  3. 信号到进程内的某些线程
  4. 规定一个特定线程以接收进程的所有信号。

发送信号的方法依赖于信号的类型。

4.5.3 线程撤销

线程撤销是在线程完成之前来终止线程。

如:多线程并发执行以搜索数据库,如果一个线程得到了结果,那么其他线程可以撤销。

要撤销的线程通常称为目标线程。目标线程的撤销可在如下两种情况下发生:

一是异步撤销(asynchronous cancellation):一个线程立即终止目标线程。

二是延迟撤销(deferred cancellation):目标线程不断地检查它是否应终止,这允许目标线程有机会以有序方式来终止自己。

如果资源已经分配给要撤销的线程,或者要撤销的线程正在更新与其他线程所共享的数据,那么撤销会有困难,对于异步撤销尤为麻烦。操作系统回收撤销线程的系统资源,但是通常不回收所有资源。因此,异步撤销线程并不会使所需的资源空闲。相反采用延迟撤销时,允许目标线程检查是否处在安全的撤消点,然后再撤销。

4.5.4 线程本地存储

同属一个进程的线程共享进程数据。

在某些情况下每个线程可能需要他自己的某些数据,这种数据称为线程本地存储(线程特定数据)。可以让每个线程与其唯一的标识符相关联。

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值