第十二章 并发编程 学习笔记

第十二章 并发编程

进程是程序级并发,线程是函数级并发。

三种基本的构造并发程序的方法:

  • 进程:每个逻辑控制流是个一个进程,由内核进行调度和维护。
  • I/O多路复用:应用程序在一个进程的上下文中显式地调度他们自己的逻辑流。
  • 线程:运行在单一进程上下文中的逻辑流,由内核进行调度。

 

12.1 基于进程的并发编程

构造并发程序最简单的方法就是用进程。

使用大家都很熟悉的函数例如:

  • fork
  • exec
  • waitpid

关于在父、子进程间共享状态信息:共享文件表,但不共享用户地址空间。

进程又独立的地址空间既是优点又是缺点:

  • 优点:防止虚拟存储器被错误覆盖
  • 缺点:开销高,共享状态信息才需要IPC机制

 

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

就是使用select函数要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。

select函数处理类型为fd_set的集合,也叫做描述符集合。

select函数有两个输入:一个称为读集合的描述符集合和该妒忌和该读集合的基数(n)(实际上是任何描述符集合的最大基数)。select函数会一直阻塞,直到读集合中至少有一个描述符准备好可以读。当且仅当一个从该描述符读取一个字节的请求不会阻塞时,描述符K就表示准备好可以读了。

    作为一个副作用,select修改了参数fdset指向的fd_set,指明读集合中一个称为准备好集合的子集。函数返回的值指明了准备好的集合的基数。由于这个副作用,我们必须在每次调用select函数时都更新集合。

I/O多路复用可以用作并发事件驱动程序的基础。

服务器使用I/O多路复用,借助select函数检测输入事件的发生。

12.3 基于线程的并发编程

线程就是运行在进程上下文中的逻辑流。

基于线程的逻辑流结合了基于进程和基于I/O多路复用的流的特性。

每个进程开始生命周期时都是单一线程,这个线程称为主线程。在某一时刻,主线程创建一个对等线程,从这个时间点开始,两个线程就并发地运行。

在一些重要的方面,线程执行是不同于进程的,因为一个线程的上下文比一个进程的上下文小很多,线程的上下文切换比进程的上下文快得多。

另一个不同就是线程不像进程那样,不是按照严格的父子层次来组织的。和一个进程相关的线程组成一个对等(线程)池,独立于其他线程创建的线程。

主线程总是进程中第一个运行的线程。对等(线程)池的概念的主要影响是,一个线程可以杀死它的任何对等线程,或者等待它的任意对等线程终止。另外,每个对等线程都能读写相同的共享数据。

Posix线程是C程序中处理线程的一个标准接口。基本用法是:

  • 线程的代码和本地数据被封装在一个线程例程
  • 每个线程例程都以一个通用指针为输入,并返回一个通用指针。

这里需要提到一个万能函数的概念。

万能函数:

void func(void parameter)
typedef void
 (uf)(void para)

即,输入的是指针,指向真正想要传到函数里的数据,如果只有一个就直接让指针指向这个数据,如果是很多就将它们放到一个结构体中,让指针指向这个结构体。后面这个方法就是万能函数的使用思想。

线程例程也是这样的。

创建线程:

 

终止线程:

回收已终止线程的资源:

分离进程:

初始化线程:

 

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

一个变量是共享的,当且仅当多个线程引用这个变量的某个实例。

一组并发线程运行在一个进程的上下文中。

每个线程都有它自己的线程上下文

  • 一个唯一的整数线程ID——TID
  • 栈指针
  • 程序计数器
  • 通用目的寄存器
  • 条件码

线程化的C程序中变量根据他们的存储类型被映射到虚拟存储器:

  • 全局变量
  • 本地自动变量
  • 本地静态变量

变量v是共享的——当且仅当它的一个实例被一个以上的线程引用。

 

12.5 用信号量同步线程

共享变量十分方便,但也引入了同步错误的可能性。

这里有个关键点:一般而言,你没有办法预测操作系统是否将为你的线程选择一个正确的顺序。

进度图:

进度图是将n个并发线程的执行模型化为一条n维笛卡尔空间中的轨迹线,原点对应于没有任何线程完成一条指令的初始状态。

当n=2时,状态比较简单,是比较熟悉的二维坐标图,横纵坐标各代表一个线程,而转换被表示为有向边

转换规则:

  • 合法的转换是向右或者向上,即某一个线程中的一条指令完成
  • 两条指令不能在同一时刻完成,即不允许出现对角线
  • 程序不能反向运行,即不能出现向下或向左

而一个程序的执行历史被模型化为状态空间中的一条轨迹线

 信号量:可以解决同步不同执行线程问题,具有非负整数值的全局变量,只能由两种特殊的操作来处理,这两种操作被称为:

12.7 其他并发问题

当用线程编写程序时,我们必须小心地编写那些具有称为线程安全性属性的函数。

一个函数被称为线程安全的当且仅当被多个并发线程反复地调用时,它会一直产生正确的结果。

如果一个函数不是线程安全的,我们就称为线程不安全的。

 

有一类重要的线程安全函数,叫可重入函数,当他们被多个线程调用时,不会引用任何共享数据。

可重入的包括显式可重入函数和隐式可重入函数。

 

posted on 2015-12-06 18:47  20135232  阅读(208)  评论(0编辑  收藏  举报