Apache HTTP Server 版本 2.4
可用语言: en
在 Apache HTTP Server 2.x 中使用任何线程化的 MPM 时,务必确保从 Apache 调用的每个函数都是线程安全的。在链接第三方扩展时,可能难以确定最终的服务器是否线程安全。通常,简单的测试也无法确定这一点,因为线程安全问题会导致难以察觉的竞争条件,这些条件可能只在高负载的特定情况下才会出现。
在编写模块或尝试确定模块或第三方库是否线程安全时,需要牢记一些常见事项。
首先,您需要认识到,在多线程模型中,每个线程都有自己的程序计数器、堆栈和寄存器。局部变量存储在堆栈中,因此它们是安全的。您需要注意任何静态或全局变量。这并不意味着您绝对不能使用静态或全局变量。在某些情况下,您实际上希望某些内容影响所有线程,但通常情况下,如果您希望代码线程安全,则需要避免使用它们。
如果您有一个需要全局访问且所有线程都可以访问的全局变量,那么在更新它时要格外小心。例如,如果它是一个递增计数器,则需要原子地递增它以避免与其他线程发生竞争条件。您可以使用互斥锁(互斥)来实现这一点。锁定互斥锁,读取当前值,递增它,写回它,然后解锁互斥锁。任何想要修改该值的另一个线程都必须首先检查互斥锁,并在互斥锁被清除之前阻塞。
如果您使用的是 APR,请查看 apr_atomic_*
函数和 apr_thread_mutex_*
函数。
这是一个常见的全局变量,它保存最后发生的错误的错误号。如果一个线程调用一个设置 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
。
不仅要线程安全,还要可重入。strtok()
是一个明显的例子。您第一次调用它时,它会使用分隔符,然后它会记住该分隔符,并在每次后续调用时返回下一个标记。显然,如果多个线程调用它,您将遇到问题。大多数系统都有一个可重入版本的函数,称为 strtok_r()
,您可以在其中传递一个额外的参数,该参数包含一个已分配的 char *
,该函数将使用它来代替自己的静态存储来维护标记状态。如果您使用的是 APR,您可以使用 apr_strtok()
。
crypt()
是另一个往往不可重入的函数,因此,如果您在库中遇到对该函数的调用,请注意。不过,在某些系统上它是可重入的,因此它并不总是问题。如果您的系统有 crypt_r()
,您可能应该使用它,或者如果可能,只需使用 md5 来避免整个混乱。
以下是第三方 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)
来查找模块中的单个符号。
如果您对该列表有任何补充或更正,请告知 [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_locks 、CRYPTO_set_locking_callback 、CRYPTO_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