apache / 2.4 / reference / developer-thread_safety.html

Apache HTTP Server 2.x 线程安全问题

在 Apache HTTP Server 2.x 中使用任何线程 mpms 时,从 Apache 调用的每个函数都是线程安全的,这一点很重要。在链接第三方扩展时,可能很难确定结果服务器是否是线程安全的。随便测试通常也不会告诉您这两种情况,因为线程安全性问题可能导致微妙的竞争状况,而这种竞争状况仅在重载下的某些状况下才会出现。

全局变量和静态变量

在编写模块或尝试确定模块或第三方库是否是线程安全的时,请记住一些常见的事项。

首先,您需要认识到在线程模型中,每个线程都有自己的程序计数器,堆栈和寄存器。局部变量存在于堆栈中,所以很好。您需要当心任何静态或全局变量。这并不意味着绝对不允许您使用静态或全局变量。有时候您确实希望某些东西影响所有线程,但是如果您希望代码是线程安全的,通常就需要避免使用它们。

如果您有一个需要全局变量并被所有线程访问的全局变量,则在更新它时要非常小心。例如,如果它是一个递增计数器,则需要自动对其进行递增,以避免与其他线程发生竞争。您可以使用互斥锁(互斥)来执行此操作。锁定互斥锁,读取当前值,将其递增并写回,然后解锁互斥锁。任何其他想要修改值的线程都必须首先检查互斥量并阻塞,直到将其清除为止。

如果您使用的是APR,请查看apr_atomic_*函数和apr_thread_mutex_*函数。

errno

这是一个通用全局变量,用于保存最近发生的错误的错误号。如果一个线程调用设置 errno 的低级函数,然后另一个线程对其进行检查,则我们会将错误号从一个线程渗入另一个线程。要解决此问题,请确保您的模块或库定义_REENTRANT或使用-D_REENTRANT进行编译。这将使 errno 成为每个线程的变量,并有望对代码透明。它通过执行以下操作来做到这一点:

#define errno (*(__errno_location()))

这意味着访问 errno 将调用 libc 提供的__errno_location()。设置_REENTRANT还会强制将其他一些功能重新定义为*_r等效项,有时还会将通用getc/putc宏更改为更安全的函数调用。检查您的 libc 文档以了解详细信息。代替_REENTRANT或除_REENTRANT之外,可能会影响此符号的符号还有_POSIX_C_SOURCE_THREAD_SAFE_SVID_SOURCE_BSD_SOURCE

常见的标准麻烦功能

事情不仅必须是线程安全的,而且还必须是可重入的。 strtok()很明显。您第一次使用分隔符来调用它,然后它会记住该分隔符,并且在随后的每次调用中,它将返回下一个标记。显然,如果有多个线程在调用它,那么您将遇到问题。大多数系统都有一个称为strtok_r()的函数的可重入版本,您可以在其中传递一个额外的参数,该参数包含一个已分配的char *,该函数将使用该参数代替其自己的静态存储来维护令牌化状态。如果您使用的是APR,则可以使用apr_strtok()

crypt()是另一个倾向于不可重入的函数,因此,如果您在库中遇到对该函数的调用,请当心。但是在某些系统上它是可重入的,因此并不总是一个问题。如果您的系统有crypt_r()的机会,则应该使用它,或者如果可能的话,只需使用 md5 来避免整个混乱。

通用第三方 Library

以下是第三方 Apache 模块使用的常见库的列表。您可以使用ldd(1)nm(1)之类的工具来检查模块是否正在使用潜在的不安全库。例如,对于PHP,请尝试以下操作:

% ldd libphp4.so libsablot.so.0 => /usr/local/lib/libsablot.so.0 (0x401f6000) libexpat.so.0 => /usr/lib/libexpat.so.0 (0x402da000) libsnmp.so.0 => /usr/lib/libsnmp.so.0 (0x402f9000) libpdf.so.1 => /usr/local/lib/libpdf.so.1 (0x40353000) libz.so.1 => /usr/lib/libz.so.1 (0x403e2000) libpng.so.2 => /usr/lib/libpng.so.2 (0x403f0000) libmysqlclient.so.11 => /usr/lib/libmysqlclient.so.11 (0x40411000) libming.so => /usr/lib/libming.so (0x40449000) libm.so.6 => /lib/libm.so.6 (0x40487000) libfreetype.so.6 => /usr/lib/libfreetype.so.6 (0x404a8000) libjpeg.so.62 => /usr/lib/libjpeg.so.62 (0x404e7000) libcrypt.so.1 => /lib/libcrypt.so.1 (0x40505000) libssl.so.2 => /lib/libssl.so.2 (0x40532000) libcrypto.so.2 => /lib/libcrypto.so.2 (0x40560000) libresolv.so.2 => /lib/libresolv.so.2 (0x40624000) libdl.so.2 => /lib/libdl.so.2 (0x40634000) libnsl.so.1 => /lib/libnsl.so.1 (0x40637000) libc.so.6 => /lib/libc.so.6 (0x4064b000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000)

除了这些库之外,您还需要查看静态链接到模块中的所有库。您可以使用nm(1)在模块中查找单个符号。

Library List

如果您对此列表有补充或更正,请在dev@httpd.apache.org上添加 Comments。

Library Version Thread Safe? Notes
ASpell/PSpell ?
Berkeley DB 3.x, 4.x Yes 跨线程共享连接时要小心。
bzip2 Yes 低级和高级 API 都是线程安全的。但是,高级 API 需要对 errno 进行线程安全的访问。
cdb ?
C-Client Perhaps c-client 使用strtok()gethostbyname(),它们在大多数 C 库实现中都不是线程安全的。 c Client 端的静态数据旨在跨线程共享。如果strtok()gethostbyname()在您的 os 上是线程安全的,则 c-client 可能是线程安全的。
libcrypt ?
Expat Yes 每个线程需要一个单独的解析器实例
FreeTDS ?
FreeType ?
GD 1.8.x ?
GD 2.0.x ?
gdbm No 通过静态gdbm_error变量返回的错误
ImageMagick 5.2.2 Yes ImageMagick 文档声称它从 5.2.2 版本开始是线程安全的(请参阅Change log)。
Imlib2 ?
libjpeg v6b ?
libmysqlclient Yes 使用 mysqlclient_r 库变量来确保线程安全。有关更多信息,请阅读http://dev.mysql.com/doc/mysql/en/Threaded_clients.html
Ming 0.2a ?
Net-SNMP 5.0.x ?
OpenLDAP 2.1.x Yes 使用ldap_r库变体以确保线程安全。
OpenSSL 0.9.6g Yes 需要正确使用CRYPTO_num_locksCRYPTO_set_locking_callbackCRYPTO_set_id_callback
liboci8(Oracle 8) 8.x,9.x ?
pdflib 5.0.x Yes PDFLib 文档声称它是线程安全的; changes.txt 表示自 V1.91:http://www.pdflib.com/products/pdflib-family/pdflib/起它已部分处于线程安全状态。
libpng 1.0.x ?
libpng 1.2.x ?
libpq (PostgreSQL) 8.x Yes 不要在线程之间共享连接,请注意crypt()个呼叫
Sablotron 0.95 ?
zlib 1.1.4 Yes 依赖于线程安全的 zalloc 和 zfree 函数默认情况下使用线程安全的 libc 的 calloc/free。