1 文件和目录
随着时间的推移,在存储虚拟化过程中形成了两个关键的抽象概念。第一个是文件。文件只是一个由字节组成的线性数组,每个字节都可以读写。每个文件都有某种底层名称,通常是某个数字;通常情况下,用户并不知道这个名称(我们将看到)。由于历史原因,文件的底层名称通常被称为其inode number(索引节点号)。
到目前为止,我们在写并发性的时候,好像构建并发应用程序的唯一方法就是使用线程。就像生活中的许多事情一样,这并不完全正确。具体来说,基于图形用户界面的应用程序和某些类型的互联网服务器经常使用不同风格的并发编程。这种风格被称为基于事件的并发,已在一些现代系统中流行起来,包括 node.js 等服务器端框架,但其根源在于 C/UNIX 系统,我们将在下文讨论。
基于事件的并发解决了两个方面的问题。首先,在多线程应用程序中正确管理并发性是一项挑战;正如我们所讨论的,可能会出现锁丢失、死锁和其他令人讨厌的问题。其次,在多线程应用程序中,开发人员几乎无法控制特定时刻的调度;相反,程序员只需创建线程,然后寄希望于底层操作系统以合理的方式在可用 CPU 上调度这些线程。由于很难构建一个通用的调度程序,在所有情况下都能很好地处理所有工作负载,操作系统有时会以非最佳的方式调度工作。因此,我们的关键是:
到目前为止,我们已经开发了锁的概念,并了解了如何通过正确的硬件和操作系统支持组合来正确构建锁。不幸的是,锁并不是构建并发程序所需的唯一原语。
特别是,在很多情况下,线程希望在继续执行之前检查条件是否为真。例如,父线程可能希望在继续之前检查子线程是否已完成(这通常称为 join());这样的等待应该如何实现呢?我们来看下面这段代码。