程序员的自我修养[链接/装载/库] 第一章笔记


  • 技术优劣取决于需求

  • 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决.

  • 计算机系统软件体系结构的设计要点:按层次结构设计的

    每个层次之间需要相互通信.通信协议,接口.

    接口的上层为接口使用者.下层为接口提供者.

  • 操作系统的一个功能就是提供抽象的接口.另一个功能就是管理硬件资源.

    一个计算机中的资源主要为 CPU 存储器(内存&磁盘) IO设备

  • 所有的应用程序都是以 进程 的方式运行

    每个进程有自己独立的地址空间.使得进程间的地址空间相互隔开.

    CPU由操作系统同意进行分配.每个进程根据进程优先级的高低都有机会获得CPU.

    但若运行时间超过一定时间,操作系统会暂停该进程.将CPU资源分配给其他等待运行的进程.

    抢占式.

    操作系统可以强制剥夺CPU资源并且分配给它认为目前最需要的进程.


线程基础

什么是线程

  • 线程,轻量级进程,是程序执行的最小单元.
  • 一个标准的线程由线程ID,当前命令指针PC,寄存器集合和堆栈组成.

线程的访问权限

  • 线程的访问是非常自由的.它可以访问进程内存里的所有数据.甚至包括其他线程的堆栈(若它知道其他线程的堆栈地址的话).实际中,线程拥有自己的存储空间.
线程私有 线程之间共享(进程所有)
局部变量 全局变量
函数参数 堆上的数据
函数里的静态变量
程序代码,任何线程都有权限读取并执行
打开的文件

线程的调度与优先级

  • 当线程数小于处理器数量.并行,否则,现成的并发会受到一些阻碍.

  • 一个处理器切换不同的线程的行为称作线程调度.

  • 在线程调度中,线程通常拥有至少三个状态:

    运行 就绪 等待
  • 线程的调度方法:

    优先级调度:线程都拥有各自的线程优先级.

    轮转法:让各个线程都执行一小段时间.

  • 线程的优先级不仅可以由用户手动设置,系统还会根据不同线程的表现自动调整优先级.

  • 在优先级调度下,存在一种 饿死 现象.即优先级低的线程执行前,总是有较高优先级的线程要执行.

  • 线程优先级改变的三种方法:

    • 用户指定优先级
    • 根据进入等待状态的频繁程度提升or降低优先级
    • 长时间得不到执行而被提升优先级

线程安全

  • 可访问的全局变量和堆数据随时可能被其他线程改变..因此多线程程序并发时数据的一致性就变得非常重要.

  • 竞争与原子操作

    单指定操作!称为原子! Windows 有提供了一套原子操作API

    但原子操作只适用于简单特定情境.

  • 同步与锁

    为了避免多个线程同时读写同一个数据而产生不可预测的后果.需要对同一个数据的访问同步.

    所谓的同步即一个线程在访问时,其他线程不能对同一个数据进行操作.

    同步最常见的方法就是用锁!

    每一个线程在访问数据哦人资源前线试图获得锁.并在访问结束后释放锁.锁是一种非强制机制,每一个线程在访问数据或资源之前首先试图获得锁.并在访问结束之后释放锁.在锁已经被占用时,试图获得锁,线程就会等待,直至锁重新可用.

    • 二元信号量

      只有两种状态.占用与非占用.它使用于智只能被唯一一个线程独占访问的资源.

    • 多元信号量(Semaphore)

      一个初始值为N的信号量允许N个线程并发访问.

      线程访问资源时首先先获取信号量,进行如下操作:

      1. 将信号量的值减1
      2. 如果信号量为小于0则进入等待状态,否则继续执行

      访问完资源之后,线程释放信号量,及逆行如下操作:

      1. 将信号量的值加1
      2. 若信号量的值小于1,唤醒一个等待中的线程
    • 互斥量(Mutex)

      与二元信号量类似

    • 临界区

      比互斥锁更加严格的同步手段.

      进入临界区:即获得临界区的锁.

      离开临界区:即释放锁

      与互斥量与信号量的区别:

    互斥量/信号量 临界区
    互斥量与信号量在系统任何进程里都可见. 临界区的作用范围仅限于本进程
    • 读写锁

      对于一段数据,多个线程要同时读取总是没问题的.但假设操作都不是原子型.只要有一个线程对这个数据进行修改.就必须使用同步手段防止出错.

      更加特定的场景:读取频繁,偶尔写入的情况.互斥锁,信号量,临界区效率低.

      对于同一个锁,读写锁有两种获取方式.共享的(shared)和独占的(Exclusive)

    读写锁状态 以共享方式获取 以独占方式获取
    自由 成功 成功
    共享 成功 等待(必须等其他所有线程释放锁)
    独占 等待 等待
    • 条件变量

      对于条件变量,线程可以由两种操作

      1. 等待条件变量.一个条件变量可以被多个线程等待
      2. 唤醒条件变量.此时某个或所有等待此条件变量的线程都会被唤醒并继续执行

可重入与线程安全

  • 一个函数被重入,表示这个函数并没有执行完成.
  • 有两种情况:
    • 多个线程同时执行这个函数
    • 函数自身调用自身

过度优化

  • 锁不一定能保证线程安全

  • 不同线程的寄存器是各自独立的.

  • CPU在执行时可能为了效率交换指令的顺序.

    CPU的乱序执行能力让我们对多线程的安全保证的努力变得异常困难.

    阻止CPU换序:barrier();阻止CPU将该指令前的指令交换到barrier之后.

  • 关键字volatile:阻止过渡优化

    • 阻止编译器为了提高速度将一个变量缓存到寄存器内而不写回.
    • 阻止编译器调整操作volatile变量的指令顺序.

多线程内部情况

  • 线程的并发执行是由多处理器或操作系统调度实现的.
  • 大多数操作系统都在内核里提供线程的支持.

三种线程模型

  • 一对一模型

    线程间的并发是真正的并发.

    一对一线程的缺点:

    1. 由于许多操作系统限制内核线程的数量.
    2. 许多操作系统内核线程调度时,上下文交换的开销较大.导致用户现场的执行效率下降.
  • 一对多模型

    线程之间切换执行.

    好处:高效的上下文切换,几乎无限制线程数量

    问题:若一个用户线程阻塞.所有线程无法执行.

  • 多对多模型

    将多个用户线程映射到少数但不止一个的内核线程上.

猜你喜欢

转载自blog.csdn.net/qjh5606/article/details/79946917