SGI

SGI STL 的线程安全

SGI STL 提供了我们认为最有用的线程安全形式。本文将解释 SGI STL 实现中的一些设计决策。

客户端必须锁定共享的可变容器

SGI 的 STL 实现只有在以下情况下才是线程安全的:对不同容器的并发访问是安全的,对共享容器的并发读取访问是安全的。如果多个线程访问同一个容器,并且至少有一个线程可能进行写入操作,则用户负责确保在容器访问期间线程之间的互斥访问。

这是确保不需要并发访问的容器能够获得最佳性能的唯一方法。锁定或其他形式的同步通常开销很大,应避免在不必要的情况下使用。

客户端或其他库可以通过将底层容器操作包装在锁定获取和释放中来轻松地提供必要的锁定。例如,可以提供一个locked_queue容器适配器,它提供了一个具有原子队列操作的容器。

对于大多数客户端来说,仅仅将容器操作设为原子是不够的;需要更大粒度的原子操作。如果用户代码需要增加计数器向量中的第三个元素,仅仅保证获取第三个元素和存储第三个元素是原子操作是不够的;还需要保证中间没有其他更新发生。因此,对向量操作获取锁是毫无用处的;用户代码必须在任何情况下都提供锁定。

这个决定与 Java 设计者做出的决定不同。有两个原因。首先,出于安全原因,Java 必须保证即使在存在对容器的无保护并发访问的情况下,虚拟机的完整性也不会被破坏。这种安全约束显然不是 C++ 或 STL 的主要驱动力。其次,性能是 STL 比 Java 标准库更重要的设计目标。

另一方面,这种线程安全的概念比尝试遵循草案标准 CD2 版本的引用计数字符串实现提供的线程安全更强。这种实现要求在多个共享字符串的读取器之间进行锁定。

锁定实现

SGI STL 实现从容器实现中删除了所有非常量静态数据。唯一可能共享的静态数据存在于分配器实现中。为此,在 HP STL 中实现每类节点分配的代码被转换为在 SGI STL 分配器中进行每大小节点分配的内联代码。目前,唯一的显式锁定是在分配器内部执行的。

许多其他容器实现也应该从这种设计中受益。通常可以在不依赖于任何特定线程包或锁定原语的便携式代码中实现线程安全的容器。

Alloc.h 使用三种不同的锁定原语,具体取决于环境。此外,可以通过定义_NOTHREADS来强制执行不进行任何锁定。三种锁定方式是

最好是始终使用操作系统提供的锁定原语。不幸的是,这些原语在执行非常短的临界区(例如分配器使用的临界区)时往往表现不佳。

使用 Pthreads 在多处理器上获取并发性的分配密集型应用程序应考虑使用来自pthread_alloc.h的 pthread_alloc。它施加了这样的限制:由一个线程释放的内存只能由该线程重新分配。但是,它通常因此获得了显著的性能优势。


[Silicon Surf] [STL Home]
版权所有 © 1999 Silicon Graphics, Inc. 保留所有权利。 商标信息