20145319 《信息安全系统设计基础》第十三周课后总结
一 教材内容总结
基本概念
- 并发:逻辑控制流在时间上重叠
- 并发程序:使用应用级并发的应用程序称为并发程序
- 三种基本的构造并发程序的方法
- 进程:用内核来调用和维护,有独立的虚拟地址空间,显式的进程间通信机制
- I/O多路复用,应用程序在一个进程的上下文中显式的调度控制流。逻辑流被模型化为状态机。
- 线程,运行在一个单一进程上下文中的逻辑流。由内核进行调度,共享同一个虚拟地址空间
基于进程的并发编程
- 常用构造并发进程的函数
fork
exec
waitpid
- 基于进程的并发服务器(以一个基于进程的echo服务器为例说明几点)
- 必须包括一个一个SIGCHLD处理程序,来回收僵死子进程的资源
- 父子进程必须关闭各自的connfd拷贝,以避免存储器泄露
- 因为套接字的文件表表项中的引用计数,直到父子进程的connfd都关闭了,到客户端的连接才会终止
- 关于进程的优劣
- 优点:一个进程不可能不小心覆盖两一个进程的虚拟存储器,可以防止虚拟存储器被错误覆盖
- 缺点:独立的地址空间使得进程共享状态信息变得更加困难,开销很高
基于I/O多路复用的并发编程
- I/O多路复用技术的优劣
- 优点:
- 比起基于进程的设计给了程序员更多的对程序行为的控制
- 运行在单一进程上下文中,因此,每个逻辑流都能访问该进程的全部地址空间,使得流之间共享数据变得很容易
- 事件驱动设计比较高效,因为不需要进程上下文切换来调度新的流
- 缺点:
- 编码复杂
- 不能充分利用多核处理器
- 优点:
基于线程的并发编程
- 多个进程运行在单一进程的上下文中,因此共享这个进程的虚拟地址空间的所有内容
- 线程执行模型
- 主线程:每个进程开始时都是单一线程,该线程被称作主线程
- 对等线程:某个时刻,主线程创建的进程
- 对等线程池:线程不同于进程是按照父子层次组织的,而是组成一个对等池
- 影响:线程可以杀死它的任何对等线程,或等待对等线程终止每个对等线程读写时候共享数据
- 线程执行不同于进程执行,因为线程上下文比较小,所以上下文切换较快
- Posix线程
- 线程例程:线程的代码和本地数据被封装在一个线程例程中。每一个线程例程都以一个通用指针作为输入,并返回一个通用指针
- 创建线程
pthread_create函数
:int pthread_create(pthread_t *tid, pthread_attr *attr, func *f, void *arg)
,创建一个新线程,并在新线程上下文中运行线程例程f,attr参数通常为NULL- 返回的参数tid包含新线程的ID,可以通过调用
pthread_self
来获得
- 终止线程
- 顶层的线程范例返回,线程会隐式的终止
- 调用
pthread_exit
,线程会显示的终止 - 对等线程调用Unix的
exit函数
,终止进程以及与进程相关的所有线程 - 对等现场通过当前线程ID调用
pthread_cancle函数
- 回收已经终止线程的资源
- pthread _join函数会阻塞,直到线程tid终止,回收已终止线程占用的所有存储器资源。pthread _join函数只能等待一个指定的线程终止
- 分离线程
- 可结合:一个可结合的线程能被其他线程杀死或者回收资源
- 分离:不能被其他线程回收或者杀死,其存储器资源在它终止时自动释放
- 默认情况下,线程被创建成都是可结合的
- 初始化线程
int pthread _once(pthread_once_t *once_control, void(*init_routine)(void))函数
允许初始化与线程例程相关的状态。once _control
变量是一个全局或者静态变量,总是被初始化为PTHREAD _ONCE _INIT
多线程程序中的变量共享
- 线程存储器模型
- 每个线程有自己独立的线程上下文,但是共享剩余的进程上下文
- 存储器是不共享的,但是虚拟存储器是共享的
- 变量映射到存储器
- 局变量:虚拟存储器的读/写区域只会包含每个全局变量的一个实例。
- 本地自动变量:定义在函数内部但没有static属性的变量。
- 本地静态变量:定义在函数内部并有static属性的变量
- 共享变量
- 若变量v是共享的,当且仅当它的一个实例被一个以上的线程引用
用信号量同步线程
- 共享变量引入了同步错误的可能
- 线程I的循环代码分解成五部分
- Hi:在循环头部的指令块
- Li:加载共享变量cnt到寄存器%eax的指令,%eax表示线程i中的寄存器%eax的值
- Ui:更新(增加)%eax的指令
- Si:将%eaxi的更新值存回到共享变量cnt的指令
- Ti:循环尾部的指令块
- 进度图
- 安全轨迹线:绕开不安全区的轨迹线
- 不安全轨迹线:接触到任何不安全区的轨迹线就叫做不安全轨迹线
- 任何安全轨迹线都能正确的更新共享计数器
- 信号量
- 当有多个线程在等待同一个信号量时,你不能预测V操作要重启哪一个线程。
- 信号量不变性:一个正在运行的程序绝不能进入这样一种状态,也就是一个正确初始化了的信号量有一个负值
- 使用信号量实现互斥
- 二元信号量:将每个共享变量与一个信号量s联系起来,然后用P(S)和V(s)操作将这种临界区包围起来
- 互斥锁:以提供互斥为目的的二元信号量
- 利用信号量来调度共享资源
- 生产者—消费者问题:
- 生产者产生项目并把他们插入到一个有限的缓冲区中,消费者从缓冲区中取出这些项目,然后消费它们
- 读者-写者问题:
- 读者优先,要求不让读者等待,除非已经把使用对象的权限赋予了一个写者。
- 写者优先,要求一旦一个写者准备好可以写,它就会尽可能地完成它的写操作
- 生产者—消费者问题:
其他并发问题
- 线程安全性:
- 线程安全:当且仅当被多个并发线程反复地调用时,它会一直产生正确的结果
- 线程不安全的类
- 不保护共享变量的函数
- 保持跨越多个调用的状态的函数。
- 返回指向静态变量的指针的函数(解决办法:重写函数和加锁拷贝)
- 调用线程不安全函数的函数
- 可重入性
- 可重入函数:当它们被多个线程调用时,不会引用任何共享数据
- 显式可重入:没有指针,没有引用静态或全局变量
- 隐式可重入:允许它们传递指针
- 竞争
- 竞争:当一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达它的控制流中的x点时,就会发生竞争
- 消除方法:动态的为每个整数ID分配一个独立的块,并且传递给线程例程一个指向这个块的指针
- 死锁
- 一组线程被阻塞了,等待一个永远也不会为真的条件(例如进程p1拥有资源R1等待资源R2,进程P2又有资源R2等待资源R1)
三 代码托管
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/1 | 20/20 | 学习常用linux命令 |
第二周 | 100/100 | 1/2 | 20/40 | 学习vim,gdb等用法 |
第三周 | 100/200 | 1/3 | 15/55 | |
第四周 | 0/300 | 0/3 | 10/65 | |
第五周 | 100/400 | 1/4 | 15/80 | 重温了汇编相关知识 |
第六周 | 0/400 | 1/5 | 15/95 | 学习了Y86 |
第七周 | 100/500 | 1/6 | 15/110 | 学习了存储器相关知识 |
第八周 | 0/500 | 2/8 | 20/130 | 复习 |
第九周 | 150/650 | 2/10 | 15/145 | 学习了I/O相关知识 |
第十周 | 300/950 | 2/12 | 20/165 | 学习了linux命令代码 |
第十一周 | 200/1150 | 3/15 | 20/185 | 学习了异常流相关知识 |
第十二周 | 200/1350 | 3/18 | 20/205 | 复习I/O,fork相关代码 |
第十三周 | 150/1500 | 2/20 | 20/225 | 学习了并发,WEB相关知识 |