第十三周学习笔记

第十二章 并发编程

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

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

  • 进程:每个逻辑控制流是个一个进程,由内核进行调度和维护。
  • 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
栈
栈指针
程序计数器
条件码
通用目的寄存器值。

每个线程和其他线程一起共享进程上下文的剩余部分。

寄存器是从不共享的,而虚拟存储器总是共享的。

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

全局变量
本地自动变量(不共享)
本地静态变量

12.5 用信号量同步线程

共享变量引入了同步错误。

进度图:

轨迹线示例:

临界区(不安全区):

12.6 使用线程来提高并行性

并行程序的加速比通常定义为:

其中,p为处理器核的数量,T为在p个核上的运行时间。

12.7 其他并发问题

线程安全

定义四个(不相交的)线程不安全函数类:

  • 不保护共享变量的函数。
  • 保持跨越多个调用状态的函数。
  • 返回指向静态变量指针的函数。
  • 调用线程不安全函数的函数。

竞争

当一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达他的控制流x点时,就会发生竞争。

为消除竞争,我么可以动态地为每个整数ID分配一个独立的块,并且传递给线程例程一个指向这个块的指针。

死锁

死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。

  • 程序员使用P和V操作顺序不当,以至于两个信号量的禁止区域重叠。
  • 重叠的禁止区域引起了一组称为死锁区域的状态。
  • 死锁是一个相当难的问题,因为它是不可预测的。

互斥锁加锁顺序规则:如果对于程序中每对互斥锁(s,t),给所有的锁分配一个全序,每个线程按照这个顺序来请求锁,并且按照逆序来释放,这个程序就是无死锁的。

参考资料

1、深入理解计算机系统课本

2、百度百科

3、http://www.cnblogs.com/tymjava/p/5024202.html谈愈敏同学博客

 

posted @ 2015-12-06 20:10  20135221黄卫  阅读(159)  评论(0编辑  收藏  举报