<-
Apache > HTTP Server > 文档 > 版本 2.4 > 开发者文档

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

可用语言:  en 

在 Apache HTTP Server 2.x 中使用任何线程化的 MPM 时,务必确保从 Apache 调用的每个函数都是线程安全的。在链接第三方扩展时,可能难以确定最终的服务器是否线程安全。通常,简单的测试也无法确定这一点,因为线程安全问题会导致难以察觉的竞争条件,这些条件可能只在高负载的特定情况下才会出现。

Support Apache!

另请参阅

top

全局变量和静态变量

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

首先,您需要认识到,在多线程模型中,每个线程都有自己的程序计数器、堆栈和寄存器。局部变量存储在堆栈中,因此它们是安全的。您需要注意任何静态或全局变量。这并不意味着您绝对不能使用静态或全局变量。在某些情况下,您实际上希望某些内容影响所有线程,但通常情况下,如果您希望代码线程安全,则需要避免使用它们。

如果您有一个需要全局访问且所有线程都可以访问的全局变量,那么在更新它时要格外小心。例如,如果它是一个递增计数器,则需要原子地递增它以避免与其他线程发生竞争条件。您可以使用互斥锁(互斥)来实现这一点。锁定互斥锁,读取当前值,递增它,写回它,然后解锁互斥锁。任何想要修改该值的另一个线程都必须首先检查互斥锁,并在互斥锁被清除之前阻塞。

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

top

errno

这是一个常见的全局变量,它保存最后发生的错误的错误号。如果一个线程调用一个设置 errno 的底层函数,然后另一个线程检查它,那么我们就会将一个线程的错误号泄漏到另一个线程。为了解决这个问题,请确保您的模块或库定义了 _REENTRANT 或使用 -D_REENTRANT 编译。这将使 errno 成为一个每个线程的变量,并且希望对代码是透明的。它是通过执行类似以下操作来实现的

#define errno (*(__errno_location()))

这意味着访问 errno 将调用 __errno_location(),该函数由 libc 提供。设置 _REENTRANT 还会强制重新定义一些其他函数,使其等效于 *_r,有时还会将常见的 getc/putc 宏更改为更安全的函数调用。有关详细信息,请查看您的 libc 文档。除了 _REENTRANT 之外,可能影响此行为的符号还有 _POSIX_C_SOURCE_THREAD_SAFE_SVID_SOURCE_BSD_SOURCE

top

常见的标准问题函数

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

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

top

常见的第三方库

以下是第三方 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) 来查找模块中的单个符号。

top

库列表

如果您对该列表有任何补充或更正,请告知 [email protected]

版本线程安全?备注
ASpell/PSpell ?
Berkeley DB 3.x, 4.x 注意跨线程共享连接。
bzip2 低级和高级 API 都是线程安全的。但是,高级 API 需要对 errno 进行线程安全访问。
cdb ?
C-Client 可能 c-client 使用 strtok()gethostbyname(),它们在大多数 C 库实现中都不是线程安全的。c-client 的静态数据旨在跨线程共享。如果 strtok()gethostbyname() 在您的操作系统上是线程安全的,那么 c-client 可能 是线程安全的。
libcrypt ?
Expat 每个线程需要一个单独的解析器实例
FreeTDS ?
FreeType ?
GD 1.8.x ?
GD 2.0.x ?
gdbm 通过静态 gdbm_error 变量返回错误
ImageMagick 5.2.2 ImageMagick 文档声称它从 5.2.2 版本开始是线程安全的(请参阅 变更日志)。
Imlib2 ?
libjpeg v6b ?
libmysqlclient 使用 mysqlclient_r 库变体以确保线程安全。有关更多信息,请阅读 https://dev.mysqlserver.cn/doc/mysql/en/Threaded_clients.html
Ming 0.2a ?
Net-SNMP 5.0.x ?
OpenLDAP 2.1.x 使用 ldap_r 库变体以确保线程安全。
OpenSSL 0.9.6g 需要正确使用 CRYPTO_num_locksCRYPTO_set_locking_callbackCRYPTO_set_id_callback
liboci8 (Oracle 8+) 8.x,9.x ?
pdflib 5.0.x 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 不要跨线程共享连接,并注意 crypt() 调用
Sablotron 0.95 ?
zlib 1.1.4 依赖于线程安全的 zalloc 和 zfree 函数 默认情况下使用 libc 的 calloc/free,它们是线程安全的。

可用语言:  en 

top

评论

注意
这不是问答部分。此处发布的评论应针对改进文档或服务器的建议,如果这些建议已实施或被认为无效/与主题无关,则可能会被我们的版主删除。有关如何管理 Apache HTTP Server 的问题应发送到我们的 IRC 频道 #httpd(在 Libera.chat 上)或发送到我们的 邮件列表