这里要说的是Unix socket API的一个缺点。假设,网站服务器使用了多个Listen语句以监听多个端口或者多个地址, Apache会使用select(2)以检测每个socket是否就绪。 select(2)会表明一个socket有 0 或者 至少一个 连接正等候处理。 由于Apache的模型是多子进程的,所有空闲进程会同时检测新的连接。 一个很天真的实现方法是这样的(这些例子并不是源代码,只是为了说明问题而已):

for (;;)
{

for (;;)
{
fd_set accept_fds;

FD_ZERO (&accept_fds);

for (i = first_socket; i <= last_socket; ++i)
{
FD_SET (i, &accept_fds);
}
rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
if (rc < 1) continue;
new_connection = -1;

for (i = first_socket; i <= last_socket; ++i)
{

if (FD_ISSET (i, &accept_fds))
{
new_connection = accept (i, NULL, NULL);
if (new_connection != -1) break;
}
}
if (new_connection != -1) break;
}
process the new_connection;
}
这种天真的实现方法有一个严重的“饥饿”问题。如果多个子进程同时执行这个循环, 则在多个请求之间,进程会被阻塞在select, 随即进入循环并试图accept此连接, 但是只有一个进程可以成功执行(假设还有一个连接就绪), 而其余的则会被阻塞在accept。 这样,只有那一个socket可以处理请求,而其他都被锁住了,直到有足够多的请求将它们唤醒。 此“饥饿”问题在PR#467中有专门的讲述。 至少有两种解决方案.
一种方案是使用非阻塞型socket,不阻塞子进程并允许它立即继续执行。 但是,这样会浪费CPU时间。设想一下,select有10个子进程, 当一个请求到达的时候,其中9个也被唤醒,并试图accept此连接, 继而进入select循环,而无所事事,并且其间, 没有一个子进程能够响应出现在其他socket的请求,直到退出select循环。 总之,这个方案效率并不怎么高,除非你有很多的CPU,而且开了很多的子进程。
另一种也是Apache所使用的方案是,使内层循环的入口串行化,形如 (不同之处以高亮度显示):

for (;;)
{
accept_mutex_on ();

for (;;)
{
fd_set accept_fds;

FD_ZERO (&accept_fds);

for (i = first_socket; i <= last_socket; ++i)
{
FD_SET (i, &accept_fds);
}
rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
if (rc < 1) continue;
new_connection = -1;

for (i = first_socket; i <= last_socket; ++i)
{

if (FD_ISSET (i, &accept_fds))
{
new_connection = accept (i, NULL, NULL);
if (new_connection != -1) break;
}
}
if (new_connection != -1) break;
}
accept_mutex_off ();
process the new_connection;
} 函数accept_mutex_on和accept_mutex_off 实现了一个互斥的信号灯,在任何时刻只能为一个进程所拥有。实现互斥的方法有多种, 其定义位于src/conf.h(1.3以前的版本) 或src/include/ap_config.h (1.3及更新版本)中。在有些根本没有锁定机制的体系中,使用多个Listen指令就是不安全的。
posted on 2005-04-29 16:26 王骏的BLOG 阅读(849)
评论(0) 编辑 收藏