Библиотека сайта 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 ... много серверов хороших и разных | Вперед |