天天看点

南京大学《操作系统》笔记(二)

并行:允许多个执行流同时执行(要求多个处理器);

并发:多个执行流可以不按照一个特定的顺序执行。

南京大学《操作系统》笔记(二)

并发性的来源:进程会调用操作系统的API,操作系统在并发执行两个执行流的时候,要保证它们互不干扰,因此,对处理器和内存提出了要求。

处理器

内存

典型的并发系统

并发/并行

单处理器

共享内存

OS内核/多线程程序

并发不并行

多处理器

并发、并行

不共享内存

分布式系统(消息通信)

多线程之间,寄存器组的值、堆栈(包括堆栈中的局部变量)是互相独立的;代码是共享的。

在C++中,可以使用pthread_create和pthread_join得到共享代码、共享数据、独立堆栈的多个线程。(will新开一个博客写)

Q1:每个线程的堆栈范围和大小?

A1:每个线程有8MB的内存映射区域和前后各4KB的保护页。

Q2:每个线程有8M的堆栈,那么为什么1000个线程没有耗尽内存?

A2:因为虽然每个线程有8MB的内存区域,但是它们是虚拟内存,并且在没有被使用的时候不会映射到物理内存上(Lazy allocate),因此物理内存的占用并没有那么多。

编译器优化->顺序的丧失:指令的执行不保证按照代码书写的顺序发生;

中断/并行->原子性的丧失:代码的原子性随时被破坏;

乱序执行->可见性的丧失:执行过的指令可能在多处理器之间不可见。

从状态机的理论去理解程序,程序的状态(内存/寄存器的快照)可以看成有限状态机的节点,程序的运行可以看成状态转换的过程。假如系统上有16MB的内存,即16*8Mb,那么可以有2^(16*8M)种状态。

大部分状态有唯一的后续状态,执行一条指令可以得到确定的结果。不确定的指令可能有多个后续状态,例如执行rdrand指令(生成真随机数),会对rax指令赋予不同的值,因此可以带给状态机不确定性。

对于程序来说,不确定性的来源可能是:

(时间)rdtsc/rdtscp:获取处理器的“时间戳”用于精确定时;

(机器状态)rdrand:处理器自身提供的“真”随机数指令;

(系统调用)syscall:应用程序的最大不确定性来源,包括用户的键入、磁盘文件的读取等。

在计算机硬件上的应用:高性能处理器实现,当状态机的执行具有确定性的时候,A->B->C的状态转换可以优化为A->C。

在计算机系统上的应用:程序分析技术。静态分析是根据程序代码推导出状态机的性质;动态分析是运行时观测到状态机的执行。

程序的执行是随着时间“前进”的(\(s_1\)->\(s_2\)->\(s_3\)->...),能否在时间上“后退”(Time-travel)?记录所有的状态\(s_i\),就能实现time-traveling。

如果记录下所有的状态,就可以回到任意一个状态去查看当时的信息。但难点在于,上文讨论过,状态的数量非常庞大,这样的记录非常耗费空间。已知每条指令只会对状态中很少一部分的寄存器/内存进行修改。

由此一个好主意是只记录初始状态和每条指令前后状态的diff。

gdb提供了这样的time-travel debugging功能:

target record-full 开始记录(需要程序开始执行);

record stop 结束记录;

reverse-step/reverse-stepi “时间旅行调试”。

系统中有n个线程,线程之间是共享内存的,则并发程序的状态s = (M, R1, R2, R3, ..., Rn)

并发系统执行指令的顺序是不确定的,每个(M, R1), (M, R2), (M, R3), ...都可以看成是一个串行程序,在任意一个状态,可以选择任意一个线程执行一条指令。

即使每个线程都是确定的,在任意一个时刻,处理器可以选择任意线程执行,此处带有不确定性,因此即使没有上述的程序三大不确定性来源,多线程程序也拥有天生的不确定性。

假如有n个线程,每个线程都执行m个在线程和时间上都独一无二的操作,那么从初始状态到结束状态(程序运行结束)是O(n^(mn)),这是一个非常大的数值。

状态机是理解程序执行的重要工具。

程序是指令序列的静态描述,高度概括、精简,行为有时难以理解;

状态机是所有动态行为的集合,将静态时的分支、循环全部展开为了顺序结构,行为明确、容易理解。

C++11 新标准中引入了五个头文件来支持多线程编程,它们分别是 <code>&lt;atomic&gt;, &lt;thread&gt;, &lt;mutex&gt;, &lt;condition_variable&gt;</code> 和 <code>&lt;future&gt;</code>。

<code>&lt;atomic&gt;</code>:该头文主要声明了两个类, <code>std::atomic</code> 和 <code>std::atomic_flag</code>,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。

<code>&lt;thread&gt;</code>:该头文件主要声明了 <code>std::thread</code> 类,另外 <code>std::this_thread</code> 命名空间也在该头文件中。

<code>&lt;mutex&gt;</code>:该头文件主要声明了与互斥量(Mutex)相关的类,包括 <code>std::mutex_*</code> 一系列类,<code>std::lock_guard</code>, <code>std::unique_lock</code>, 以及其他的类型和函数。

<code>&lt;condition_variable&gt;</code>:该头文件主要声明了与条件变量相关的类,包括 <code>std::condition_variable</code> 和 <code>std::condition_variable_any</code>。

<code>&lt;future&gt;</code>:该头文件主要声明了 <code>std::promise</code>, <code>std::package_task</code> 两个 Provider 类,以及 <code>std::future</code> 和 <code>std::shared_future</code> 两个 Future 类,另外还有一些与之相关的类型和函数,<code>std::async()</code> 函数就声明在此头文件中。

使用示例:(编译时需要加上-std=c++11 -pthread)

&lt;thread&gt;头文件的摘要如下,声明了std::thraed线程类和std::swap辅助函数,以及命名空间std::this_thread。

std::thread类如下。