Эффективное прослушивание нескольких сокетов, даже если файловые дескрипторы недоступны

Допустим, у меня есть две библиотеки (A и B), и в каждой есть одна функция, которая слушает сокеты. Эти функции используют select() и немедленно возвращают какое-то событие, если данные поступили, в противном случае они ждут некоторое время (timeout), а затем возвращают NULL:

A_event_t* A_wait_for_event(int timeout);
B_event_t* B_wait_for_event(int timeout); 

Теперь я использую их в своей программе:

int main (int argc, char *argv[]) {
// Init A
// Init B
// .. do some other initialization
    A_event_t *evA;
    B_event_t *evB;
    for(;;) {
        evA = A_wait_for_event(50);
        evB = B_wait_for_event(50);
        // do some work based on events
    }
}

Каждая библиотека имеет свои собственные сокеты (например, сокет udp) и недоступна извне.

ПРОБЛЕМА: это не очень эффективно. Если, например, есть много событий, ожидающих доставки с помощью *B_wait_for_event*, им придется всегда ждать, пока не истечет время ожидания *A_wait_for_event*, что эффективно ограничивает пропускную способность библиотеки B и моей программы.

Обычно можно использовать потоки для разделения обработки, НО что, если обработка некоторого события требует вызова функции другой библиотеки и наоборот. Пример:

if (evA != 0 && evA == A_EVENT_1) {
    B_do_something();
}
if (evB != 0 && evB == B_EVENT_C) {
    A_do_something();
}

Таким образом, даже если бы я мог создать два потока и отделить функциональность от библиотек, эти потоки должны были бы обмениваться событиями между собой (вероятно, через канал). Это по-прежнему будет ограничивать производительность, потому что один поток будет заблокирован функцией *X_wait_for_event()*, и будет невозможно немедленно получить данные из другого потока.

Как это решить?


person Sasa    schedule 04.04.2012    source источник
comment
Не создавайте один поток для каждой библиотеки: создайте один поток для каждой независимой вещи, которую вы хотите сделать.   -  person    schedule 04.04.2012
comment
Я не уверен, что понимаю это совершенно правильно. Что, если библиотеки не являются потокобезопасными? Как я могу вызвать функцию библиотеки A из потока T2, если T1 фактически инициализировал эту библиотеку и теперь ожидает событий?   -  person Sasa    schedule 04.04.2012


Ответы (3)


(Я перемещаю это в ответ, так как он слишком длинный для комментария)

Если вы находитесь в ситуации, когда вам не разрешено вызывать A_do_something в одном потоке, в то время как другой поток выполняет A_wait_for_event (и аналогично для B), то я почти уверен, что вы не можете ничего сделать. эффективными, и должны урегулировать между различными пороками.

Наиболее очевидным улучшением является немедленное действие при получении события, а не попытка чтения из обоих: т. е. порядок цикла

  • Дождитесь события А
  • Может быть, сделать что-то в B
  • Дождитесь события B
  • Может быть, сделать что-то в A

Другие меры, которые вы могли бы сделать, это

  • Попытайтесь предсказать, какое событие, скорее всего, произойдет дальше, событие А или событие Б, и ждите его в первую очередь. (например, если они приходят полосами, то после получения и обработки события A вы должны вернуться к ожиданию другого события A)
  • Поиграйте со значениями тайм-аута, чтобы найти баланс между циклами вращения и слишком большой блокировкой. (возможно, даже подстроиться динамически)

РЕДАКТИРОВАТЬ: вы можете проверить API для своей библиотеки; они могут уже предложить способ решения проблемы. Например, они могут позволить вам регистрировать обратные вызовы для событий и получать уведомления о событиях посредством обратного вызова, а не путем опроса wait_for_event.

Другое дело, если вы можете создать новые файловые дескрипторы для прослушивания библиотекой. например Если вы создадите новый канал и передадите один конец библиотеке A, тогда, если поток №1 ожидает события A, поток №2 сможет записать в канал, чтобы событие произошло, тем самым вытеснив №1 из wait_for_event. С возможностью по желанию исключать потоки из функций wait_for_event становятся доступными всевозможные новые опции.

person Community    schedule 04.04.2012

Это решение может быть недоступно в зависимости от используемых вами библиотек, но лучшим решением будет не вызывать функции в отдельных библиотеках, которые ожидают событий. Каждая библиотека должна поддерживать подключение к внешнему циклу событий. Затем ваше приложение использует один цикл, который содержит вызов poll() или select(), ожидающий всех событий, которых хотят ожидать все библиотеки, которые вы используете.

цикл событий glib подходит для этого, потому что многие библиотеки уже знают, как к нему подключиться. Но если вы не используете что-то столь сложное, как glib, нормальный подход таков:

  • Loop forever:
    • Start with an infinite timer and an empty set of file descriptors
    • For each library you use:
      • Call a setup function in the library which is allowed to add file descriptors to your set and/or shorten (but not lengthen) the timeout.
    • Беги poll()
    • For each library you use:
      • Call a dispatch function in the library that responds to any events that might have occurred when the poll() returned.

Да, более ранняя библиотека по-прежнему может истощать более позднюю библиотеку, но на практике это работает.

Если библиотеки, которые вы используете, не поддерживают такой интерфейс установки и отправки, добавьте его как функцию и предоставьте исходный код!

person Celada    schedule 04.04.2012
comment
Ваше решение тоже очень хорошее. Я посмотрел в API глубже, и я нашел функцию, которая возвращает сокет, который используется для сигнализации внутренних событий в библиотеке. Теперь я могу интегрировать эту библиотеку в свой цикл событий. Большое спасибо! - person Sasa; 06.04.2012

Возможным решением является использование двух потоков для wait_for_events плюс boost::condition_variable в «основном» потоке, который «что-то делает». Похожим, но не точным решением является здесь

person megabyte1024    schedule 04.04.2012
comment
Это хорошее решение, за исключением того, что даже если один подчиненный поток завершается, основной поток должен передать событие вторым подчиненным потокам, которые все еще заблокированы wait_for_events. - person Sasa; 04.04.2012