company_banner

io_submit: альтернатива epoll, о которой вы никогда не слышали

Original author: Marek Majkowski
  • Translation


Недавно внимание автора привлекла статья на LWN о новом интерфейсе ядра для опроса (polling). В ней обсуждается новый механизм опроса в Linux AIO API (интерфейс для асинхронной работы с файлами), который добавили в ядро версии 4.18. Идея довольно интересная: автор патча предлагает использовать Linux AIO API для работы с сетью.

Но постойте! Ведь Linux AIO был создан для работы с асинхронным вводом-выводом с диска / на диск! Файлы на диске — это не то же самое, что сетевые соединения. Возможно ли вообще использовать Linux AIO API для работы с сетью?

Оказывается, да, возможно! В этой статье объясняется, как использовать сильные стороны Linux AIO API для создания более быстрых и лучших сетевых серверов.

Но давайте начнём с разъяснения, что представляет собой Linux AIO.

Введение в Linux AIO


Linux AIO предоставляет интерфейс асинхронного ввода-вывода с диска / на диск для пользовательского ПО.

Исторически на Linux все дисковые операции блокировались. Если вы вызываете open(), read(), write() или fsync(), то поток останавливается до тех пор, пока метаданные не появятся в дисковом кеше. Обычно это не вызывает проблем. Если у вас не много операций ввода-вывода и достаточно памяти, системные вызовы постепенно заполнят кеш, и всё будет работать достаточно быстро.

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

Для решения этой проблемы приложения могут использовать три способа:

  1. Использовать пулы потоков и вызывать блокирующие функции в отдельных потоках. Именно так работает POSIX AIO в glibc (не путайте его с Linux AIO). Подробные сведения можно получить в документации IBM. Именно так мы решили проблему в Cloudflare: для вызова read() и open() мы используем пул потоков.
  2. Прогревать дисковый кеш с помощью posix_fadvise(2) и надеяться на лучшее.
  3. Использовать Linux AIO в сочетании с файловой системой XFS, открывая файлы с флагом O_DIRECT и избегая недокументированных проблем.

Однако ни один из этих способов не идеален. Даже Linux AIO при бездумном использовании может блокироваться в вызове io_submit(). Это недавно упоминалось в другой статье на LWN:
«У интерфейса асинхронного ввода-вывода в Linux много критиков и мало сторонников, но большинство людей ожидает от него хотя бы асинхронности. На деле же операция AIO может блокироваться в ядре по целому ряду причин в ситуациях, когда вызывающий поток не может себе этого позволить».
Теперь, когда мы знаем о слабых сторонах Linux AIO API, давайте рассмотрим его сильные стороны.

Простая программа с использованием Linux AIO


Для того чтобы использовать Linux AIO, вам сначала придётся самостоятельно определить все пять необходимых системных вызовов — glibc их не предоставляет.

  1. Сначала нужно вызвать io_setup() для инициализации структуры aio_context. Ядро вернёт нам непрозрачный (opaque) указатель на структуру.
  2. После этого можно вызвать io_submit(), чтобы добавить в очередь на обработку вектор «контрольных блоков ввода-вывода» в виде структуры struct iocb.
  3. Теперь, наконец, мы можем вызвать io_getevents() и ждать от неё ответа в виде вектора структур struct io_event — результатов работы каждого из блоков iocb.

Есть восемь команд, которые вы можете использовать в iocb. Две команды для чтения, две — для записи, два варианта fsync и команда POLL, которую добавили в версии ядра 4.18 (восьмая команда — NOOP):

IOCB_CMD_PREAD = 0,
IOCB_CMD_PWRITE = 1,
IOCB_CMD_FSYNC = 2,
IOCB_CMD_FDSYNC = 3,
IOCB_CMD_POLL = 5,   /* from 4.18 */
IOCB_CMD_NOOP = 6,
IOCB_CMD_PREADV = 7,
IOCB_CMD_PWRITEV = 8,

Структура iocb, которая передаётся в функцию io_submit, достаточно крупная и предназначена для работы с диском. Вот её упрощённая версия:

struct iocb {
  __u64 data;           /* user data */
  ...
  __u16 aio_lio_opcode; /* see IOCB_CMD_ above */
  ...
  __u32 aio_fildes;     /* file descriptor */
  __u64 aio_buf;        /* pointer to buffer */
  __u64 aio_nbytes;     /* buffer size */
...
}

Полная структура io_event, которую возвращает io_getevents:

struct io_event {
  __u64  data;  /* user data */
  __u64  obj;   /* pointer to request iocb */
  __s64  res;   /* result code for this event */
  __s64  res2;  /* secondary result */
};

Пример. Простая программа, которая читает файл /etc/passwd с помощью Linux AIO API:

fd = open("/etc/passwd", O_RDONLY);

aio_context_t ctx = 0;
r = io_setup(128, &ctx);

char buf[4096];
struct iocb cb = {.aio_fildes = fd,
                  .aio_lio_opcode = IOCB_CMD_PREAD,
                  .aio_buf = (uint64_t)buf,
                  .aio_nbytes = sizeof(buf)};
struct iocb *list_of_iocb[1] = {&cb};

r = io_submit(ctx, 1, list_of_iocb);

struct io_event events[1] = {{0}};
r = io_getevents(ctx, 1, 1, events, NULL);

bytes_read = events[0].res;
printf("read %lld bytes from /etc/passwd\n", bytes_read);

Полные исходники, конечно, доступны на GitHub. Вот вывод strace этой программы:

openat(AT_FDCWD, "/etc/passwd", O_RDONLY)
io_setup(128, [0x7f4fd60ea000])
io_submit(0x7f4fd60ea000, 1, [{aio_lio_opcode=IOCB_CMD_PREAD, aio_fildes=3, aio_buf=0x7ffc5ff703d0, aio_nbytes=4096, aio_offset=0}])
io_getevents(0x7f4fd60ea000, 1, 1, [{data=0, obj=0x7ffc5ff70390, res=2494, res2=0}], NULL)

Всё прошло хорошо, но чтение с диска не было асинхронным: вызов io_submit заблокировался и выполнил всю работу, функция io_getevents выполнилась мгновенно. Мы могли попробовать читать асинхронно, но это требует флага O_DIRECT, с которым дисковые операции идут в обход кеша.

Давайте лучше проиллюстрируем то, как io_submit блокируется на обычных файлах. Вот аналогичный пример, который показывает вывод strace в результате чтения блока объёмом 1 Гб из /dev/zero:

io_submit(0x7fe1e800a000, 1, [{aio_lio_opcode=IOCB_CMD_PREAD, aio_fildes=3, aio_buf=0x7fe1a79f4000, aio_nbytes=1073741824, aio_offset=0}]) \
    = 1 <0.738380>
io_getevents(0x7fe1e800a000, 1, 1, [{data=0, obj=0x7fffb9588910, res=1073741824, res2=0}], NULL) \
    = 1 <0.000015>

Ядро потратило 738 мс на вызов io_submit и только 15 нс — на io_getevents. Подобным образом оно ведёт себя и с сетевыми соединениями — вся работа делается io_submit.


Фото Helix84 CC/BY-SA/3.0

Linux AIO и сеть


Реализация io_submit достаточно консервативна: если переданный дескриптор файла не был открыт с флагом O_DIRECT, то функция просто блокируется и выполняет указанное действие. В случае с сетевыми соединениями это означает, что:

  • для блокирующих соединений IOCV_CMD_PREAD будет ждать ответного пакета;
  • для неблокирующих соединений IOCB_CMD_PREAD вернёт код -11 (EAGAIN).

Такая же семантика используется и в обычном системном вызове read(), поэтому можно сказать, что io_submit при работе с сетевыми соединениями не умнее старых добрых вызовов read() / write().

Важно отметить, что запросы iocb выполняются ядром последовательно.

Несмотря на то, что Linux AIO не поможет нам с асинхронными операциями, его можно использовать для объединения системных вызовов в пакеты (batches).

Если веб-серверу нужно отправить и получить данные из сотен сетевых соединений, то использование io_submit может оказаться отличной идеей, поскольку позволит избежать сотен вызовов send и recv. Это улучшит производительность — переход из пользовательского пространства в ядро и обратно не бесплатен, особенно после введения мер по борьбе со Spectre и Meltdown.

Один буфер
Несколько буферов
Один файловый дескриптор
read()
readv()
Несколько файловых дескрипторов
io_submit + IOCB_CMD_PREAD
io_submit + IOCB_CMD_PREADV

Для иллюстрации группировки системных вызовов в пакеты с помощью io_submit давайте напишем небольшую программу, которая пересылает данные из одного TCP-соединения в другое. В простейшей форме (без Linux AIO) она выглядит примерно так:

while True:
  d = sd1.read(4096)
  sd2.write(d)

Тот же функционал мы можем выразить через Linux AIO. Код в этом случае будет таким:

struct iocb cb[2] = {{.aio_fildes = sd2,
                      .aio_lio_opcode = IOCB_CMD_PWRITE,
                      .aio_buf = (uint64_t)&buf[0],
                      .aio_nbytes = 0},
                     {.aio_fildes = sd1,
                     .aio_lio_opcode = IOCB_CMD_PREAD,
                     .aio_buf = (uint64_t)&buf[0],
                     .aio_nbytes = BUF_SZ}};
struct iocb *list_of_iocb[2] = {&cb[0], &cb[1]};
while(1) {
  r = io_submit(ctx, 2, list_of_iocb);

  struct io_event events[2] = {};
  r = io_getevents(ctx, 2, 2, events, NULL);
  cb[0].aio_nbytes = events[1].res;
}

Этот код добавляет два задания в io_submit: сначала запрос на запись в sd2, а потом запрос на чтение из sd1. После выполнения чтения код исправляет размер буфера записи и повторяет цикл сначала. Есть одна хитрость: первый раз запись происходит с буфером размера 0. Это необходимо потому, что у нас есть возможность объединить write + read в одном вызове io_submit (но не read + write).

Быстрее ли этот код, чем обычные read() / write()? Пока нет. Обе версии используют два системных вызова: read + write и io_submit + io_getevents. Но, к счастью, код можно улучшить.

Избавляемся от io_getevents


Во время выполнения io_setup() ядро выделяет несколько страниц памяти для процесса. Вот как этот блок памяти выглядит в /proc//maps:

marek:~$ cat /proc/`pidof -s aio_passwd`/maps
...
7f7db8f60000-7f7db8f63000 rw-s 00000000 00:12 2314562     /[aio] (deleted)
...

Блок памяти [aio] (12 Кб в данном случае) был выделен io_setup. Он используется для кольцевого буфера, где хранятся события. В большинстве случаев нет причин для вызова io_getevents — данные о завершении событий можно получить из кольцевого буфера без необходимости перехода в режим ядра. Вот исправленная версия кода:

int io_getevents(aio_context_t ctx, long min_nr, long max_nr,
                 struct io_event *events, struct timespec *timeout)
{
    int i = 0;

    struct aio_ring *ring = (struct aio_ring*)ctx;
    if (ring == NULL || ring->magic != AIO_RING_MAGIC) {
        goto do_syscall;
    }

    while (i < max_nr) {
        unsigned head = ring->head;
        if (head == ring->tail) {
            /* There are no more completions */
            break;
        } else {
            /* There is another completion to reap */
            events[i] = ring->events[head];
            read_barrier();
            ring->head = (head + 1) % ring->nr;
            i++;
        }
    }

    if (i == 0 && timeout != NULL && timeout->tv_sec == 0 && timeout->tv_nsec == 0) {
        /* Requested non blocking operation. */
        return 0;
    }

    if (i && i >= min_nr) {
        return i;
    }

do_syscall:
    return syscall(__NR_io_getevents, ctx, min_nr-i, max_nr-i, &events[i], timeout);
}

Полная версия кода доступна на GitHub. Интерфейс этого кольцевого буфера плохо документирован, автор адаптировал код из проекта axboe/fio.

После этого изменения наша версия кода с использованием Linux AIO требует только одного системного вызова в цикле, что делает её чуть быстрее, чем оригинальный код с использованием read + write.


Фото  Train Photos CC/BY-SA/2.0

Альтернатива epoll


С добавлением IOCB_CMD_POLL в ядро версии 4.18 стало возможным использование io_submit в качестве замены select/poll/epoll. Например, этот код будет ожидать данных от сетевого соединения:

struct iocb cb = {.aio_fildes = sd,
                  .aio_lio_opcode = IOCB_CMD_POLL,
                  .aio_buf = POLLIN};
struct iocb *list_of_iocb[1] = {&cb};

r = io_submit(ctx, 1, list_of_iocb);
r = io_getevents(ctx, 1, 1, events, NULL);

Полный код. Вот его вывод strace:

io_submit(0x7fe44bddd000, 1, [{aio_lio_opcode=IOCB_CMD_POLL, aio_fildes=3}]) \
    = 1 <0.000015>
io_getevents(0x7fe44bddd000, 1, 1, [{data=0, obj=0x7ffef65c11a8, res=1, res2=0}], NULL) \
    = 1 <1.000377>

Как видите, в этот раз асинхронность сработала: io_submit выполнилась мгновенно, а io_getevents заблокировалась на одну секунду в ожидании данных. Это можно использовать вместо системного вызова epoll_wait().

Более того, работа с epoll обычно требует использования системных вызовов epoll_ctl. А разработчики приложений стараются избегать частых вызовов этой функции — чтобы понять причины, достаточно прочитать в мануале о флагах EPOLLONESHOT и EPOLLET. Используя io_submit для опроса соединений, можно избежать этих сложностей и дополнительных системных вызовов. Просто добавьте соединения в вектор iocb, вызовите io_submit один раз и ожидайте выполнения. Всё очень просто.

Резюме


В этом посте мы рассмотрели Linux AIO API. Этот API изначально задумывался для работы с диском, но он работает также и с сетевыми соединениями. Однако, в отличие от обычных вызовов read() + write(), использование io_submit позволяет группировать системные вызовы и таким образом увеличивать производительность.

Начиная с ядра версии 4.18 io_submit и io_getevents в случае с сетевыми соединениями могут быть использованы для событий вида POLLIN и POLLOUT. Это является альтернативой epoll().

Могу себе представить сетевой сервис, который использует только io_submit и io_getevents вместо стандартного набора read, write, epoll_ctl и epoll_wait. В этом случае группировка системных вызовов в io_submit может дать большое преимущество, такой сервер был бы значительно быстрее.

К сожалению, даже после недавних улучшений Linux AIO API дискуссия о его полезности продолжается. Хорошо известно, что Линус его ненавидит:

«AIO — это ужасный пример дизайна «на коленке», где основное оправдание: «другие, менее одарённые люди придумали это, поэтому мы вынуждены соблюдать совместимость ради того, чтобы разработчики баз данных (которые редко обладают вкусом) могли использовать это». Но AIO всегда был очень-очень кривым».

Было предпринято несколько попыток создать лучший интерфейс для группировки вызовов и асинхронности, однако им не хватило общего видения. Например, недавнее добавление sendto(MSG_ZEROCOPY) позволяет вести действительно асинхронную передачу данных, но не предусматривает группировки. io_submit предусматривает группировку, но не асинхронность. Ещё хуже — в Linux на данный момент есть три способа доставки асинхронных событий: сигналы, io_getevents и MSG_ERRQUEUE.

В любом случае, отлично, что появляются новые способы ускорить работу сетевых сервисов.
Badoo
438.09
Big Dating
Share post

Similar posts

Comments 32

    +5
    если он такой кривой, как тогда он окзался в апстриме?
      +1
      Думаю, этот вопрос лучше задать тому же Линусу или Эндрю Мортону. Судя по гит-репозиторию, AIO в ядре достаточно давно и многие патчи после 2005 года (т.е. после перехода на Git) были signed-off самим Линусом или Мортоном.

      На мой взгляд, это нормальная ситуация в больших opensource-проектах — да, есть части кода, которые не очень нравятся основателю. Тем не менее, они удовлетворяют нужды пользователей, не ломают другие части проекта и работают так, как задумано.
        +6
        Собственно вот то самое письмо Линуса всё объясняет: Oracle работал на Linux'е медленнее, чем на Solaris'е, потому пришлось реализовать что-то похожее.

        Это было, понятно, в те времена, когда основной системой на серверах был именно Solaris, а не Linux… и теперь это выкинуть невозможно, так как совместимость же!
        0
        А вы не думали об использовании SolarFlare?
          0
          Честно говоря, мы не используем это в Badoo — все узкие места в бизнес-логике, а не в обработке сетевых соединений, поэтому нам (пока?) вполне хватает libevent.
          А пост — перевод.
            0
            Да, к концу статьи я уже упустил, что это не ваши приключения, а перевод )
              0
              Я, кстати, на днях пытался найти aio в PHP, не нашёл, зато вышел на эту статью :)
                0
                а оно там точно нужно? :) я б вынес такие вещи в какой-то отдельный сервис на чем-то вроде C или Go.
                  0
                  Может и не точно :) У нас проблема интересная — в некоторых ситуациях при чтении с NFS, если сервер пропадает, клиент NFS намертво зависает где-то в ядре, вместо с процессами, дерзнувшими в этот момент обратиться к этой файловой системе.

                  Так накрепко зависает, что минус-девять не помогает.

                  Мы придумали несколько решений и костылей, aio было в числе кандидатов.
                    0
                    я б на вашем месте смотрел в сторону решения, которое предусматривает отсутствие NFS. мы выпилили NFS вообще везде примерно лет так 8 назад и ничуть не жалеем. сейчас сетевых FS и хранилищ разных много, слава богу.
                      0
                      Да, это одно из направлений по которому мы работаем. Посмотрим что получится :)
                      0
                        0
                        Такой путь мы тоже рассматриваем. Правда с реализацией от Фейсбука.
                        0
                        > Так накрепко зависает, что минус-девять не помогает.

                        Точно не забыли опции soft, intr при монтировании?
                          0
                          soft не рекомендуется же. Про intr спрошу у ребят.
                            0
                            Soft mount не рекомендуется для использования в общем случае, поскольку прикладная программа может быть не готова обрабатывать внезапные ошибки доступа к файлам.

                            Если предполагается, что с файлами на запись будет работать только ваша собственная программа, и возможность ошибки на каждой стадии будет предусмотрена — soft вполне себе допустимый вариант.

                            В случае же read-only сетевая nfs-шара в режиме soft mount и вовсе ничем принципиально не отличается от, к примеру, обычного cd-rom.
                            0
                            По второй опции вот что они сказали:
                            The intr / nointr mount option is deprecated after kernel 2.6.25.
                  +3
                  Совсем недавно появился еще git.kernel.dk/cgit/liburing
                  Небольшое описание git.kernel.dk/cgit/linux-block/commit/?h=for-next&id=8923ebc04818fcb506829591aa8704baefd661ec
                    0
                    В Линуксе вроде как уже давно существует библиотека libaio (пакет Debian libaio1), которую как раз очень хочет Oracle при установке. Как я понял при беглом просмотре это как раз обертка для системных вызовов.
                    –1
                    Вот здесь я искренне завидую разработчикам под Windows.
                    Создал IOCP, привязал сокет к нему, засабмитил несколько WSARecv(), причём можно подряд на один сокет — они будут отрабатываться в порядке поступления.
                    При отправке — никакого ожидания. Дальше слушаешь поступление сообщений в IOCP, по адресу OVERLAPPED опознаёшь выполненный запрос, разбираешь ответ.
                    Абсолютно честный проактор без странностей типа «ну мы вроде отправляем асинхронно, но на самом деле — вы тут и заблокируетесь». Ядро умеет опознавать такие заранее подсунутые буфера и складывать в них данные без копирования через промежуточный буфер — ещё с тех времён, когда большинство юниксов про zero-copy только мечтали.
                    Есть, конечно, проблемы и тут. Перемещать хэндлы между IOCP для баланса нагрузки — есть только недокументированный путь. Сокеты не во всех ожиданиях сочетаются с другими файлоподобными объектами. Нельзя заранее узнать или регулировать количество доступных активных асинхронных запросов. Но если с ними справиться — система работает на максимуме эффективности без лишних потерь.

                    Что Линусу не нравится в проакторной асинхронности — понять невозможно, а описать внятно без всех этих crap/horrible/etc. он не в состоянии. Когда-то он точно так же ругался на kqueue про «silly triplet», в результате принял в ядро то же самое, но кривее и несовместимо, под названием epoll. Здесь он не хочет ни один вариант AIO впускать полноценно (чтобы оно не блокировалось); боится плодить ядерные нити и FSM на сокеты? Честно непонятно.
                      0

                      Люди, а за что минусуете? Человек дело говорит. Меня тоже всегда удивляло, что такое сильное ядро не умеет полноценно асинхронно работать, тогда как есть пример как это нормально реализовано в другом ядре.

                        0
                        Не минусовал, но предположу.
                        IOCP легко эмулируется через epoll(), есть статья сравнения на хабре habr.com/ru/company/infopulse/blog/415403
                        т.е. достаточно написать свой небольшой враппер — и удобство точно такое же.

                        По скорости медленнее тоже врядли будет, в свое время я перепробовал целую кучу методов, включая IOCP, TransmitFile, WSAEnumNetworkEvents… В простом кейсе легкий запрос-ответ, на винде было примерно 60к rps против 110k rps на лине на той же машине.
                        Я даже пробовал недокументированные извращения с Nt* функциями, но результата это не дало.

                        А вот основная проблема, что open() и CreateFile() обращаются к метаданным на винте и тоже могут повиснуть — так и остается проблемой, приходится использовать пул потоков.
                          0

                          Спасибо за разъяснения. А почему тогда такие простые, судя по вашим словам, обёртки недоступны по умолчанию в линуксе? Вот вывод логов через rsyslog из контейнеров блокируется? Потому что мы в ЦИАН уже получали проблемы от этого пару лет назад. Возможно, что-то изменилось или мы что-то не так готовили, но при резком увеличении логов у нас питон глох, если logstash не успевал принимать логи с rsyslog машины.
                          Ну и про rps — асинхронность она же не про rps, а про масштабирование io heavy приложений (современные микросервисы) и их использование cpu на это дело.

                            0
                            При заполнении отведённого под логи буфера остаётся только два варианта: подождать или выкинуть то что не влезло. Это не зависит от асинхронности и количества потоков.
                              0

                              Это то да, но там, насколько я помню, было то, что даже отправка в syslog из приложения (чтобы на том конце уже дропнули в режиме overflow) было блокирующей операцией и это деградировало отработку запросов в приложении. И это мы говорим про udp! Блокирующая отправка по udp, Карл! Вот это меня удивляет.

                                0
                                Про обертки не понял, обертки есть, одна из известных — Boost.Asio. Если говорить про низкий уровень — то скорее у всех узкое место open() и CreateFile().

                                rsyslog честно не знаю как он оптимизирован, но я вижу проект не такой простой, там и шифрование есть github.com/rsyslog/rsyslog/tree/master/runtime
                                Я бы первым делом посмотрел strace (или htop + s на треде) и perf питоновского процесса на отсылке и сервера что шлет в сеть. Узкое место наверняка найдется.
                                Так же вижу вот такие баг репорты bugzilla.redhat.com/show_bug.cgi?id=1047039
                                там в комментах видно использование select(), что уже недопустимо. Вполне возможно все решается конфигурацией.
                            0
                            У IOCP есть преимущество следующего вида — IOCP «из коробки» поддерживает многопоточность, когда все потоки, читающие из completion port считаются Windows, как единый пул потоков с LIFO планированием. Т.е. создал completion port, навесил на него все сокеты принимаемых входящих соединений (случай сервера), создал пул потоков, где каждый поток читает по одному событию из completion port и получил почти равномерную (LIFO все таки) загрузку всех потоков из пула (при полной загрузке сервера, при частичной загрузке — за счет LIFO часть потоков может и простаивать, если задач — completion packets — им попросту «не достается»).

                            При попытке выстроить подобную архитектуру с единственным epoll instance придется использовать mutex. Это убивает всякое планирование потоков (и оно вовсе не LIFO, которое Windows IOCP как бы обещает где-то в документации) и серьезно просаживает производительность. Поэтому с epoll поступают иначе — создают несколько epoll instances (например, по кол-ву ядер CPU) и с каждым epoll instance работает только один поток без каких-либо mutex.

                            Вот только наличие нескольких epoll instance уже требует распределения (а распределение может требовать и учета — зависит от алгоритма) сокетов по этим нескольким epoll instance. Неудачное распределение может привести к перекосам в нагрузке потоков (Why does one NGINX worker take all the load?) — когда одни потоки перегружены, а другие простаивают (в то время как в других epoll instance есть необработанные события).

                            Насчет эмуляции реактора в IOCP (для тех, кто не хочет выделать буфер заранее) — тот же Boost.Asio реализвал это поверх IOCP через null_buffers (Reactor-Style Operations).

                              0
                              Проблема со скоростю многопоточности у epoll() действительно была. Но в кокой-то момент она была решена, по-моему даже после EPOLLEXCLUSIVE были косяки, но сейчас я проблем не наблюдаю — треды загружаются равномерно.
                                0
                                Судя по epoll_ctl manual page EPOLLEXCLUSIVE ввели, начиная с версии ядра 4.5. Не все могут позволить себе такое. Например, весь RHEL 7 — это Kernel Version 3.10.x (хотя можно и обмануть).

                                Похоже, нужно почитать про epoll — даже что-то вводное типа Вся правда о linux epoll уже говорит о том, что, возможно, с многопоточностью (pre-fork) все работает, если готовить определенным образом. Я не уверен, что такой рецепт подходит для всех — вроде как есть причины, по которым та же Asio все еще не использует edge triggered режим.
                                  0
                                  Ну это верно. Вообще RHEL/Centos по ощущениям может быть консервативнее дебиана, я помню ужаснулся когда увидел версию gcc там, зато его админы частенько любят. Чтобы что-то новое там использовать его готовить надо, обязательно epel подключать итд. А вот в OpenSUSE/Ubuntu/Arch все поновее и таких проблем нет.

                      Only users with full accounts can post comments. Log in, please.