第12章 并发编程

 概念:逻辑控制流在时间上重叠,就是并发的。(开始结束时间区间有交叉)
 用处:
●访问慢速I/O设备:在等待如磁盘的数据时,内核运行其他进程使CPU保持繁忙
●与人交互:计算机要有多任务能力,可以同时进行多个操作
●通过推迟工作降低延迟:推迟其他操作,在CPU空闲时候运行
●服务多个网络客户端
●多核机器上并行计算

 三种构造并发程序的方法:
●进程:控制流必须使用显式的IPC机制
●I/O多路复用:应用程序在一个进程的上下文中显示地调度自己的逻辑流。逻辑流被模型化为状态机。所有的流属于一个进程,共享同一个地址空间。
●线程:两种方式的结合,既像进程流一样由内核进行调度,又能共享同一个地址空间。

12.1基于进程的并发编程

 并发服务器工作原理:父进程接受客户端的连接请求,然后创建一个新的子进程来为每个新客户端提供服务。
 注意事项:父子进程共享文件描述符,所以建立子进程提供服务时,要关闭子进程的listenfd,关闭父进程的connfd,父进程不关闭connfd会一直消耗内存,子进程越来越多就会耗光。
在这里插入图片描述
12.1.1基于进程的并发服务器源码看书
 注意事项:
●服务器会运行很长时间,所以要包括一个SIGCHLD处理程序,来回收僵尸子进程,Linux信号不排队,SIGCHLD处理程序必须准备好回收多个僵尸子进程的资源。while(waitpid(-1),0,WNOHANG)>0)
●父子进程都要关闭connfd,不然会造成内存泄漏(虽然子进程关闭会自动释放,但是显示释放是好习惯)
●套接字文件表表项中有引用计数,直到父子进程的connfd都关闭了,到客户端的连接才会终止。

12.1.2进程的优劣
 共享文件表,但不共享用户地址空间:
●优点:防止被别的进程覆盖,保护地址空间
●缺点:必须用显示的IPC机制才能通信,如waitpid,管道、信号量

12.2 基于I/O多路复用的并发编程

 为什么要I/O多路复用:某些时候服务器必须响应两个互相独立的I/O事件:(1)网络客户端发起连接请求 (2)用户在键盘上键入命令行。如果在accept中等待连接请求,不能响应输入(阻塞)。在read中等待输入,就不能响应连接请求。

 解决办法:I/O多路复用,如使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。不阻塞时候读,就不会出现上面的情况。(用select监听这些fd的集合,本来要阻塞的行为都准备好了再做,就不会阻塞了,监听到准备好就处理准备好的fd)

 select:见书
 缺点:一旦连接到某个客户端,就会连续会送输入行,阻塞其他客户端,知道这个客户端关闭连接中它的那一端。可以更细粒度的多路复用如每次循环回送一个文本行。

11.2.1 基于I/O多路复用的并发事件驱动服务器
 原理:服务器使用I/O多路复用,借助select函数检测输入的发生和连接请求的到来,当有已连接描述符准备好可读时,服务器就为相应的状态机执行转移,在这里就是读和写回一个文本行。
在这里插入图片描述
 代码分析:见书

12.2.2 I/O多路复用技术的优劣
●优点:
1、比基于进程的设计给了程序员更多的对程序行为的控制
2、每个逻辑流都能访问该进程的全部地址空间
●缺点:
1、编码复杂,并发粒度减小,复杂性还会上升。
2、不能充分利用多核处理器

12.3 基于线程的并发编程

 优势:结合了进程和I/O多路复用的流的特性,既能由内核自动调度,又能共享虚拟地址空间。
12.3.1 线程执行模型
 与进程的不同:
●线程不像进程有严格的父子层次,和一个进程相关的线程组成一个对等线程池。一个线程可以杀死它的任何对等线程,或者等待它的任意对等线程终止。
●线程的上下文比进程的上下文小得多,因此切换快得多

12.3.2 Posix线程
 概念:Posix线程(Pthreads)是在C程序中处理线程的一个标准接口,在所有Linux系统上都可用。

12.3.3到12.3.7线程相关函数看书

12.3.8 基于线程的并发服务器
 工作原理:主线程不断地等待连接请求,然后创建一个线程处理请求
 创建线程时如何传递fd给线程:
在这里插入图片描述
 代码以及出现竞争:见书

12.4 多线程程序中的共享变量

 这一节有关于线程共享的工作原理如:
1、线程的基础内存模型
2、根据这个模型,变量如何映射到内存
3、有多少线程引用这些实例

12.4.1 线程内存模型
 特点:
●每个线程有自己独立的线程上下文,线程间共享的是进程上下文的剩余部分。
●一个线程不能修改另一个线程的寄存器值,寄存器从不共享,虚拟内存总是共享的。
●线程栈不对其他线程设防(线程通常是合作关系,所以不用设防),一个线程可以通过指针访问其他线程的栈的任何部分。

12.4.2 将变量映射到内存
 变量类型:
●全局变量:全局变量是定义在函数之的变量。运行时,虚拟内存的读/写区域只包含每个全局变量的一个实例(每个对等线程都读写这个实例),任何线程都可以引用。

●本地自动变量:是定义在函数内部但是没有static属性的变量,运行时,每线程的都包含它的所有本地自动变量的实例。

●本地静态变量:定义在函数内部并有static属性的变量。和全局变量一样,虚拟内存只包含一个实例。

12.4.3共享变量
 概念:共享说明一个变量的实例被一个以上的线程引用。

12.5 用信号量同步线程

 使用信号量的原因:没法预测线程运行的顺序

12.5.1进度图
 临界区内的指令不应该被打断,也就是说临界区的访问是互斥
在这里插入图片描述
12.5.2 信号量semaphore
 P和V确保了信号量不可能为负,P不能减时等待V唤醒,V加完1唤醒P
在这里插入图片描述
12.5.3 使用信号量来实现互斥
 如何实现:将每个共享变量与一个信号量(初始为1)联系,然后用P和V操作将相应的临界区包围起来。
 二元信号量:值总是0或1,又称为互斥锁(mutex)
 使用:
在这里插入图片描述
在这里插入图片描述
12.5.4利用信号量调度共享资源
1、生产者-消费者问题:
 概念:生产者消费者共享一个有n个槽的有限缓冲区,生产者线程生成项目,插入缓冲区,消费者取出项目并消费
 要点:
●插入和取出都涉及更新共享变量,必须保证对缓冲区的访问是互斥的
●还需要调度对缓冲区的访问,如果缓冲区是满的,生产者必须等待直到有槽位可用。如果为空,消费者也必须等待。

 代码见书
在这里插入图片描述
2、读者-写者问题:
 概念:一组并发的线程访问一个共享对象,有些线程只读对象,而有的线程修改对象。写者必须独占访问对象,读者可以和其他读者共享对象。
 类型:
●读者优先:读者不会因为有写者在等待而等待。
●写者优先:一旦写者准备好可以写,就会开始写,读者必须等待。
在这里插入图片描述
12.5.5 综合:基于预线程化的并发服务器
 预线程化:之前的并发服务器为每个新客户端创建了一个新进程,代价太大。预线程化的服务器通过生产者消费者模型来降低这种开销。
在这里插入图片描述
 代码见书
 要点:线程例程中调用初始化程序包的方法
在这里插入图片描述
 事件驱动:
在这里插入图片描述

12.6 使用线程提高并行性

 并行的概念:多处理器的并发
在这里插入图片描述
 并行对0到n求和的程序以及优化,看书
从将线程和放入共享全局变量,用互斥锁保护优化到用私有变量计算部分和,效率提升很大。因为P和V代价太大

 刻画并行程序的性能:见书

12.7 其他并发问题

 线程不安全的函数类:
1、不保护共享变量的函数:
可以用P和V保护共享变量,缺点是P和V代价大,效率不高。
2、保持跨越多个调用的状态的函数
3、返回指向静态变量的指针的函数
将计算结果放在static变量中,然后返回一个指向这个变量的指针。这种情况中一个线程使用的结果会被另一个线程覆盖。
4、调用线程不安全的函数
调用线程不安全的函数的函数不一定不安全,见书

12.7.2 可重入性
 当它们被多个线程调用时,不会引用任何共享数据。
在这里插入图片描述
12.7.3在线程化的程序中使用已存在的库函数
 可重入版本的名字总是以“_r”后缀结尾。
在这里插入图片描述
12.7.4竞争
 概念:当一个程序的正确性依赖于不同线程的运行顺序时,就会发生竞争。

 错误例子和解决措施见书

12.7.5死锁
 概念:一组线程被阻塞了,等待一个永远也不会为真的条件。
 互斥锁加锁顺序规则:给定所有互斥操作的一个全序,如果每个线程都是以一种顺序获得互斥锁并以相反的顺序释放,那么这个程序就是无死锁的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值