《程序员的自我修养》学习笔记(一):简介

1. 从Hello World说起

2. 万变不离其宗

3. 站得高,望得远

4. 操作系统做什么

不让CPU打盹

监控程序监控CPU使用——多道程序;

每个程序运行一段时间后主动让出CPU——分时系统;

多任务系统:应用程序以进程方式运行;抢占式CPU分配。

设备驱动

操作系统是对硬件抽象;

操作系统中的硬件驱动程序完成硬件细节的实现;

操作系统为硬件厂商提供一系列接口和框架。

5. 内存不够怎么办

简单分配物理内存的问题:地址空间不隔离;内存使用效率低;程序运行的地址不确定。

关于隔离:虚拟地址空间;物理地址空间。

分段(Segmentation)

把一段与程序所需的内存空间大小的虚拟空间映射到某个地址空间

解决了问题1、3

分页(Paging)

把地址空间人为地分成固定大小的页,于是产生了:虚拟页;物理页;磁盘页。

页映射

内存共享;

内存保护;

采用Memory Management Unit部件进行页映射;

CPU发出虚拟地址-->MMU转换成物理地址-->物理内存。

6. 众人拾柴火焰高

线程基础

线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成。

多个线程组成进程,线程间共享

内存空间:代码段;数据段;堆。

进程级资源:打开文件;信号。

多线程的优点:

有效利用等待时间;交互与计算;程序逻辑要求;多CPU或多核计算机;与多进程相比更高效的数据共享。

线程的访问权限

可以访问进程内存所有数据:全局变量;堆;函数内的静态变量;程序代码;打开文件。

线程私有空间:栈上数据——函数参数;线程局部存储空间(Thread Local Storage)上的数据;寄存器上数据——局部变量。

线程调度与优先级

不断在处理器上切换不同的线程——线程调度。

线程状态:运行;就绪;等待。

调度方法有轮转法和优先级调度法。

优先级设置策略

a.用户设置

b.系统设置,其考虑因素有:

进入等待的频繁程度:频繁等待——IO密集型线程;很少等待,用尽时间片——CPU密集型线程。IO密集型更容易提升优先级;

等待时间长短,等待时间过长的线程将饿死。

可抢占式线程和不可抢占式线程。抢占式为当线程用尽时间片后会被强制释放CPU,而进入就绪状态。早期系统线程不可抢占,只能线程自己发命令放弃执行,主动进入就绪状态。不可抢占线程主动放弃执行的情况:1)线程试图等待某事件;2)线程主动放弃时间片。

Linux的多线程

Linux内核不存在真正意义的线程概念。Linux将所有执行实体都称为任务(Task)——具有内存空间、执行实体、文件资源。任务间可以共享内存空间。系统调用创建新的任务:

fork:复制当前进程,与原任务一起共享写时复制的内存空间;

exec:使用新的可执行映像覆盖当前可执行映像,fork与exec配合产生新任务;

clone:创建子进程并从指定位置开始执行,用于产生新线程。

线程安全

维护并发数据的一致性对多线程程序很重要

竞争与原子操作

以自增为例子说明一句高级程序代码可能被转化为多句汇编指令,从而导致错误,而单指令的操作成为原子操作,其执行不会被打乱。CPU和操作系统提供了相应的原子操作接口。

同步与锁

同步(Synchronization),即一个线程访问数据未有结束时,其他线程不得对同一个数据进行访问,它实现了数据访问的原子化。

锁(Lock)——实现同步最常见的方法,其概念为:线程访问资源或数据前首先试图获取(Acquire)锁;访问结束时释放(Release)锁;当试图获取锁时,锁已被其它进程占用,线程将等待。

二元信号量(Binary Semaphore),最简单的锁,只有占用/非占用两种状态;

信号量(Semaphore),允许多个线程并发访问资源,初始值N的信号量允许N个线程并发访问。其操作:

线程访问资源:信号量减1;如果信号量小于0,线程进入等待状态,否则继续执行;

线程释放资源:信号量加1;如果信号量小于1,唤醒一个等待中的线程。

互斥量(Mutex),资源仅同时允许一个线程访问,与二元信号量相类似。

与二元信号量不同的是,信号量在整个系统可以被任意线程获取并释放;互斥量则要求获取和释放互斥量的是同一个线程。

临界区(Critical Section),进入临界区——锁的获取;离开临界区——锁的释放。

与信号量、互斥量的区别是,信号量、互斥量在系统的任何进程里都是可见的;临界区的作用范围仅限于本进程,其他进程无法获取该锁

读写锁(Read-Write Lock),对于同一个锁,有两种获取方式:共享的(Shared),独占的(Exclusive)。当锁处于自由时,两种获取锁的方式都成功;当锁处于共享状态时,其他线程以共享的方式获取锁仍然成功;当锁处于独占状态时,其他线程必须等待其释放。

条件变量(Condition Variable),可被多个线程等待(线程等待某个事件),条件变量被唤醒(事件发生),所有线程可以一起恢复执行。

可重入与线程安全

函数被重入,表示函数没有执行完成,由于外部因素或内部调用,又一次进入该函数执行。发生重入的情况有:多个线程同时执行这个函数;函数自身(可能经过多层调用后)调用自身。

函数可重入的条件:

不使用任何(局部)静态或全局的非const变量;

不返回任何(局部)静态或全局的非const变量的指针;

仅依赖与调用方提供的参数;

不依赖任何单个资源的锁;

不调用任何不可重入的函数。

过度优化

编译器和CPU优化带来两个问题:编译器为提高变量访问速度,将其缓存在寄存器,即使发生写操作也不写回主存(互斥问题);CPU动态调度,交换两条相邻指令的执行顺序(同步问题)。

使用volatile关键字试图阻止过度优化,该关键字能够。

1)阻止编译器为了提高速度将一个变量缓存到寄存器内而不写回;

2)阻止编译器调整操作volatile变量的指令顺序。

但它不能阻止CPU动态调用换序,文中以Singleton为例说明,指出new指令包含三步操作,多线程和CPU动态调用情况下可能导致错误。需要调用CPU提供的指令barrier阻止其进行指令交换。

多线程内部情况

内核线程由多处理器或调度实现并发。而用户实际使用的是用户态线程,并不一定在操作系统内核里对于同等数量的线程,于是产生了用户线程与内核线程的三种模型

一对一模型

线程间的并发是真正的并发,一个线程受阻塞并不影响其他线程,在多处理器上有很好表现。Linux中使用clone;Windows中使用CreateThread创建。但其缺点为:内核线程的数量有限制;内核线程调度时,上下文切换的开销较大,导致用户线程执行效率低。

多对一模型

多个用户线程映射到一个内核线程上,线程切换由用户态代码实现,速度快。但如果一个用户线程受阻塞,其他线程均无法执行,而且未能利用多处理器。

多对多模型

多个用户线程映射到多于一个但少量的内核线程上。一个用户线程阻塞并不会使所有用户线程阻塞,对用户线程数量没有限制,在多处理器系统上的运行效率也有一定提升。

原文地址:https://www.cnblogs.com/alonecat06/p/2868482.html