在早期的计算机中不包含操作系统,它们从头到尾只执行一个程序,并且这个程序能访问计算机中的所有资源。
操作系统的出现使得计算机每次运行多个程序,并且不同的程序都在单独的进程中运行:操作系统为各个独立执行的进程分配各种资源,包括内存、文件句柄、安全证书等。
不同进程中交换数据的通信机制(粗粒度):
- 套接字
- 共享内存
- 文件
- 信号量
- 信号处理器
在计算机中加入操作系统来实现多个程序同时执行的原因:
- 资源利用率。在某些情况下,程序必须等待某个外部操作执行完成,比如输入输出操作,而在等待时程序无法执行其他任何工作。因此,如果在等待的同时计算机可以运行其他程序,那么无疑将提高资源的利用率。
- 公平性。不同的用户和程序对计算机上的资源有着同等的使用权。一种高效的运行方式是通过粗粒度的时间分片(Time Slicing)使这些用户和程序能够共享计算机资源,而不是由一个程序从头运行到尾,然后再启动下一个程序。
- 便利性。通常来说,在计算多个任务时,应该编写多个程序,每个程序执行一个任务并在必要时互相通信,这比只编写一个程序来计算所有任务更容易实现。
在早期的分时系统中,每个进程相当于一台虚拟的冯诺依曼计算机,他拥有存储 指令和数据 的内存空间,根据机器语言的语义以串行方式执行指令,并通过一组I/O指令与外部设备通信。
串行编程模型的优势在于其直观性和简单性,因为它模仿了人类的工作方式:每次只做一件事情,做完之后再做另一件。
线程允许在同一个进程中同时存在多个程序控制流,线程会共享进程范围内的资源,例如内存句柄和文件句柄,但每个线程都有各自的程序计数器(Program Counter)、栈、局部变量等。
线程的优势:降低程序的开发和维护成本,提升复杂应用程序的性能,提高GUI程序的响应灵敏度,提高资源利用率和系统吞吐率。
- 发挥多处理器的强大能力
- 建模的简单性
- 异步事件的简化处理
- 响应更灵敏的用户界面
线程带来的风险:
- 安全性问题(竞态条件(Race Condition))
- 活跃性问题
- 性能问题
安全性的含义是“永远不发生糟糕的事情”
活跃性则关注另一个目标:“某件正确的事情最终会发生”。单线程程序中:无限循环;多线程程序中:死锁、饥饿、活锁。
性能问题包括服务时间过长、响应不灵敏、吞吐率过低、资源消耗过高、可伸缩性较低等。
线程带来的运行时开销:
- 在多线程程序中,当线程调度器临时挂起活跃线程并转而运行另一个线程时,就会频繁地出现上下文切换操作(Context Switch),这种造作将带来极大的开销:保存和恢复执行上下文,丢失局部性,并且CPU时间将更多地花在线程调度而不是线程运行上。
- 当线程共享数据时,必须使用同步机制,而这些机制往往会抑制某些编译器优化,使内存缓存区中的数据无效,以及增加共享内存总线的同步流量。
当某个框架在应用程序中引入并发性时,通常不可能将并发性仅仅局限于框架代码,因为框架本身会回调(Callback)应用程序的代码,而这些代码将访问应用程序的状态。