On this page
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_locks ,CRYPTO_set_locking_callback ,CRYPTO_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。 |