Библиотека сайта rus-linux.net
| Назад | Сервер TCP/IP ... много серверов хороших и разных | Вперед |
Последовательный сервер с очередью обслуживания
Уже после опубликования первоначальной статьи, читатели неоднократно указывали мне в письмах, что при акцентировании на параллельных вариантах серверов из рассмотрения опущен другой класс, который при определённых условиях может оказаться оптимальнее параллельных. Это: последовательные сервера с очередью обслуживания. Рассмотрим коротко и их (здесь нам существенно придёт на помощь то, что мы реализуем примеры в C++ и сможем не возиться с ручными реализациями очередей с C, а использовать шаблоны STL). Первый пример (ech4.cc) в высшей степени неэффективный, но прозрачен и прост в понимании идеи:
#include "common.h"
#include <pthread.h>
#include <queue>
static queue<int> events;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* reply( void* ) {
int rsq;
while( true ) {
if( events.empty() ) continue;
pthread_mutex_lock( &mutex );
rsq = events.front();
events.pop();
if( debug ) cout << '-' << rsq << '.' << flush;
pthread_mutex_unlock( &mutex );
retrans( rsq );
close( rsq );
};
}
// последовательный ретранслятор c очередью обслуживания
int main( int argc, char *argv[] ) {
int ls = getsocket( QUEUE_PORT );
setv( argc, argv );
pthread_t tid;
if( pthread_create( &tid, NULL, &reply, NULL ) != EOK )
errx( "thread create error" );
while( true ) {
int rs = accept( ls, NULL, NULL );
if( rs < 0 ) errx( "accept error" );
pthread_mutex_lock( &mutex );
events.push( rs );
if( debug ) cout << '+' << rs << '.' << flush;
pthread_mutex_unlock( &mutex );
};
exit( EXIT_SUCCESS );
};
Здесь два потока:
- первый из них (main) только принимает запросы по accept(), помещает их в очередь ожидающих запросов, и мгновенно освобождается для приёма следующих запросов;
- второй поток циклически непрерывно (и здесь и причина неэффективности) сканирует очередь ожидающих запросов, и, если в ней есть что обрабатывать, производит обработку, ответ клиенту и удаление запроса из очереди.
Вот как это работает:
$ sudo nice -n19 ./ech4 waiting on port 51008 ... $ ./cli -a 192.168.1.5 -p 51008 -n 20 host: 192.168.1.5, TCP port = 51008, number of echoes = 20 time of reply - Cycles [usec.] : 727605[237] 384249[125] 389930[127] 367517[119] 365907[119] 4467486[1455] 448396[146] 388366[126] 363228[118] 357719[116] 410907[133] 383594[124] 459195[149] 383444[124] 369783[120] 358478[116] 367218[119] 437609[142] 382846[124] 359065[116]
Времена реакции в среднем того же порядка, что и у простого последовательного сервера, но с большой дисперсией, что и обусловлено тем, что обрабатывающий поток непрерывно «перелопачивает» очередь, и потокам приходится конкурировать.
Второй вариант (ech41.cc) является улучшенным вариантом предыдущего: здесь обрабатывающий поток пассивно ожидает на условной переменной пока в очереди появится хотя бы один требующий обработки запрос. Но, в отличие от предыдущего, он будет заметно более громоздким:
#include "common.h"
#include <pthread.h>
#include <queue>
static queue<int> events;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
void* reply( void* ) {
int rsq;
while( true ) {
if( events.empty() )
pthread_cond_wait( &condvar, &mutex );
rsq = events.front();
events.pop();
if( debug ) cout < '-' < rsq < '.' < flush;
pthread_mutex_unlock( &mutex );
retrans( rsq );
close( rsq );
};
}
// последовательный ретранслятор c очередью обслуживания
int main( int argc, char *argv[] ) {
int ls = getsocket( QUEUE_PORT );
setv( argc, argv );
pthread_t tid;
if( pthread_create( &tid, NULL, &reply, NULL ) != EOK )
errx( "thread create error" );
sched_yield(); // дать выбирающему потоку заблокироваться
while( true ) {
int rs = accept( ls, NULL, NULL );
if( rs < 0 ) errx( "accept error" );
pthread_mutex_lock( &mutex );
events.push( rs );
pthread_cond_signal( &condvar ); // сообщать о наличии работы
if( debug ) cout < '+' < rs < '.' < flush;
pthread_mutex_unlock( &mutex );
};
exit( EXIT_SUCCESS );
};
И его выполнение:
$ sudo nice -n19 ./ech41 waiting on port 51008 ... $ ./cli -a 192.168.1.5 -p 51008 -n 20 host: 192.168.1.5, TCP port = 51008, number of echoes = 20 time of reply - Cycles [usec.] : 607372[197] 550517[179] 578301[188] 540316[176] 502585[163] 506989[165] 507829[165] 445947[145] 454687[148] 603485[196] 558521[181] 528724[172] 523089[170] 538304[175] 555197[180] 524572[170] 535394[174] 534911[174] 569871[185] 506437[164]
Теперь разброс задержки ответа существенно снизился, что и следовало ожидать.
| Назад | Сервер TCP/IP ... много серверов хороших и разных | Вперед |
