c - 设计线程安全库

c - designing thread-safe library

我正在开发新库以将我现有的一堆应用程序移植到通用代码段并与其他人分享我的经验 (FOSS)。

库实现本身根本不使用任何 global/static 变量。相反,它提供不透明的数据类型和相应的访问函数来对该数据执行不同的任务。

未来将使用此库的应用程序可能(并且将会)是多线程的(例如具有实时处理功能的 GUI 应用程序),因此出现了有关线程安全的问题。至于我有两种实现方式:

  1. 升级当前的库代码并实现一些线程感知的东西,例如,通过使用 PThreads API。对于我来说,它看起来是这样的:为每个潜在的可变对象添加一些互斥锁并在访问器例程中尊重它们 - setters、getters、reinitializers 等
  2. 以不会发生线程特定问题(例如死锁、并发等)的方式编写客户端程序 - 通过在最终用户应用程序中使用相同的 PThreads,而不是库。

乍一看,第一种方法更通用、更灵活——我的库的最终用户不应该担心用多线程破坏东西:库的内部将在互斥锁和其他同步机制。可能是我缺乏一些经验,但我不记得任何使用这种方法的项目。可能是我没深入挖掘...

第二种方法看起来更简单(对于作为库开发人员的我而言),因为我将所有线程同步任务卸载给最终用户,而无需自己处理 PThreads(或类似的东西)。

我的问题是:纯粹在库本身(方法 1)中处理线程安全问题是否被认为是好的做法?将所有线程同步任务卸载给最终用户会导致在使用库的同时减轻其开发的不必要的开销吗?

编辑:符合 SO 风格

我不知道是否有正确的方法,但一个好的方法是为调用代码提供一个不透明的句柄(就像你正在做的那样)并记录您提供的句柄一次只能由一个线程使用。然后由用户来确保只有一个线程持有句柄的副本(以便其他线程无法访问其资源,仅仅是因为它们无权访问句柄),或者调用代码可以共享跨线程句柄并通过互斥体显式序列化所有使用该句柄的调用。

这样做的好处是,如果用户想避免互斥锁的开销,他可以;或者如果他想(小心地)在线程之间共享句柄并序列化对它的访问,这也是一个选项。

你当然需要确保你的库代码不访问任何跨线程共享的内部资源(或者如果访问,它在这样做之前锁定共享互斥锁),但这听起来像你可能已经在这样做了。

一些使用此方法的 C 库示例:C 运行时库(其 FILE * 句柄由 fopen(), libsndfile (with its SNDFILE * handles as returned by sf_open()), OpenSSL (with its SSL * handles as returned by SSL_new() 返回)

请注意,您的选项 (1) 会增加每次调用的开销,而且可能还不够,因为如果用户想按顺序调用多个方法 atomically/together,他将需要手动锁定一个无论如何互斥。作为反例,Java 的原始 java.util.Hashtable class implements this implicit-serialization-on-every-call approach and has been deprecated,主要是因为这种线程安全方法在回顾时被视为一个错误特征。

最广泛的可移植方法是要求客户端代码提供定义或回调以原子方式在底层平台上执行一些操作,无论需要什么内存屏障,可能包括实现这些方法的库使用 C11 原子操作。原子操作优于锁,因为无需担心应用程序不能立即完成操作时应该做什么;允许客户端代码提供功能可能比使用 C11 操作更好,因为许多平台将能够直接在某些类型上处理有限范围的原子操作,而无法处理 C11 标准规定的所有操作。