Appearance
现实世界
尽管对并发原语和并行性进行了多年的研究,但使用锁进行编程仍然具有挑战性。通常最好将锁隐藏在更高级别的构造中,如同步队列,尽管 xv6 没有这样做。如果你使用锁编程,明智的做法是使用一个试图识别竞争的工具,因为很容易忽略一个需要锁的不变量。
大多数操作系统支持 POSIX 线程(Pthreads),它允许一个用户进程有多个线程在不同的 CPU 上并发运行。Pthreads 支持用户级锁、屏障等。Pthreads 还允许程序员可选地指定一个锁应该是可重入的。
在用户级别支持 Pthreads 需要操作系统的支持。例如,如果一个 pthread 在一个系统调用中阻塞,同一进程的另一个 pthread 应该能够在该 CPU 上运行。作为另一个例子,如果一个 pthread 改变了它的进程的地址空间(例如,映射或取消映射内存),内核必须安排运行同一进程线程的其他 CPU 更新它们的硬件页表以反映地址空间的变化。
可以在没有原子指令的情况下实现锁,但这很昂贵,而且大多数操作系统都使用原子指令。
如果许多 CPU 试图同时获取同一个锁,锁的开销可能会很大。如果一个 CPU 在其本地缓存中缓存了一个锁,而另一个 CPU 必须获取该锁,那么更新持有该锁的缓存行的原子指令必须将该行从一个 CPU 的缓存移动到另一个 CPU 的缓存,并可能使该缓存行的任何其他副本失效。从另一个 CPU 的缓存中获取一个缓存行可能比从本地缓存中获取一个行昂贵几个数量级。
为了避免与锁相关的开销,许多操作系统使用无锁数据结构和算法。例如,可以实现一个像本章开头那样的链表,在列表搜索期间不需要锁,并且用一个原子指令在列表中插入一个项。然而,无锁编程比使用锁编程更复杂;例如,必须担心指令和内存重排序。使用锁编程已经很困难了,所以 xv6 避免了无锁编程的额外复杂性。