Наши партнеры

UnixForum





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