В одно время всё никак не мог придумат как красиво реализовать модель с одним io-thread, который принимает запросы и множеством worker-thread которые их обрабатывают. Зачем такая схема? Потому что в многотредовом приложении только один тред может обрабатывать события (epoll, kqueue и т.п.). Но однотредовое приложение не будет использовать все ресурсы современного многоядерного процессора.
Спасибо anight, не в первый раз он меня выручает, за идею.
Идею я реализовал и снова порадовался её красоте. Решил не держать в закромах реализацию идеи, а поделиться ею.
Дла начала скачайте ioworker-0.0.2.tar.gz.
Пример кода сильно порезан чтобы не создавать лишнюю путаницу. Для меня там всё кажется очевидным :), но кратко поясню код…
Всё что делает программа — это приём запросов на заданном порту (-p
или 8000 по-умолчанию), при этом вызывается request_read_cb который ищет в запросе \n и, если находит, вызывает worker_add_task или продолжает ждать пока не придёт \n. worker_add_task последовательно перебирает все воркеры и пытается каждому из них скормить ссылку на соединение (conn_t). Если получилось, данное соединение маркируется как используемое (c->used = 1) и цикл прерывается. Если не получилось, на соединение с воркером вешается EV_WRITE (помимо того что там постоянно висит EV_READ). Когда приходит EV_WRITE (на все события по соединению с воркерами вызывается worker_cb) мы снова пытаемся скормить воркеру ссылку на соединение. В это время воркер (worker_loop) ждёт данные от io-thread. Ждёт он их на блокирующем сокете. Как только приходят данные (ссылка на соединение), в соединение пишутся ответные данные (идентификатор воркера и количество запросов к этому воркеру) и отсылаем ссылку на обработанное соединение обратно в io-thread. io-thread принимает это соединение (worker_cb) и отсылает ответ за запрос клиенту.
Вот такая вот штука. Буду рад если этот код кому-нить поможет.
Да, в программе используется libevent.
Удачи.
Tags: ioworker
Наворочено !
Зачем посылать NULL ptr воркеру, чтобы его выключить ? Достаточно закрыть неблокирующийся конец socketpair(), на другом read() сразу же завершится.
Вызывать из signal handler conn_close() и event_loopexit() нифига не безопасно.
А это что ? ;-)))
if (TCP_NODELAY) {
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags));
}
У каждого worker thread должна быть очередь соединений, для которых write(core_ctx.wsp[i], &c, sizeof(void *)) вернул EAGAIN. Если очереди не будет - все последующие соединения будут зависать (у тебя событие удаляется при поступлении нового соединения).
Ну может со временем у меня получится это упростить, это же 0.0.1
NULL ptr — это сигнал что именно всё, финита ля комедиа, но можно и как ты говоришь, согласен, проще.
Почему не безопасно? Хотя да, я не учёл что это потоки, а не процессы, сокет может закрыться когда с ним работают, фак, спасибо за замечание, исправлюсь
“А это что ? ;-)))” это претекает из кода в код… типа опцию TCP_NODELAY устанавливает для соединения.
Фак, действительно удаляется, спасибо, испралюсь
Бля, дошло про TCP_NODELAY, смеюсь
> Почему не безопасно? Хотя да, я не учёл что это потоки, а не процессы, сокет может закрыться когда с ним работают, фак, спасибо за замечание, исправлюсь
Потому что: conn_close() вызывает event_del() а он в свою очередь (кажется) вызывает free(), а это не signal-safe.
> Потому что: conn_close() вызывает event_del() а он в свою очередь (кажется) вызывает free(), а это не signal-safe.
Покопался в libevent, вроде как не вызывается free() при event_del(). Да и странно было бы, чтобы в библиотеке, которую авторы позиционируют как высокопроизводительную, на event_del() вызывалось бы free(). ИМХО.
Меня тут смутило скорее то, что может закрыться рабочее соединение, по которому ещё не всё отдано. И это не есть гут.
> Покопался в libevent, вроде как не вызывается free() при event_del(). Да и странно было бы, чтобы в библиотеке, которую авторы позиционируют как высокопроизводительную, на event_del() вызывалось бы free(). ИМХО.
Это роли не играет. event_del() не signal-safe и без free(). Хочешь ловить спонтанные SIGSEGV на загруженной системе - пожалуйста, оставляй как есть
Может я чего-то не понимаю в сигналах… Мне казалось что сигналы могу приходить (вернее выбираться из очереди) только тогда когда процесс не занят какой-то работой. Так как вся работа с event идёт внутри одного, io-thread и сигналы ловятся тоже в io-thread, то я считал, что не может произойти так, что event_del() будет вызван “в неудобный момент”.
А как бы ты завершил такую программу по сигналу?
Сигнал может прийти в процесс в произвольном месте. Погугли “async-signal safe functions”.
event_del(), вызванный из signal handler запросто может подраться с другим event_del(), который сейчас выполняется в основном процессе, но был прерван по сигналу.
Чтобы было удобно работать с сигналами, их обычно сначала делают синхронными: готовится pipe(), в который в signal handler”е записывается 1 байт, на другом конце - io thread, который пробуждается, читает байт и реагирует на сигнал. Можно и не pipe, а socketpair - не принципиально.
Осознал, спасибо, буду копать
Спасибо
Кстати, я так и не сделал сигналы синхронными, учтите…
Читал про это уже на каком то другом сайте, но у вас гораздо прикольней написано
Интересно, конечно! Спасибо! Прочитал с удовольствием и вниманием.
Хочу услышать ваше мнение, как поднять свою работоспособность. Подскажите какие нить действенные способы(методы), может о чем то таком подумать или представить стимилирующее к работе. Обычно на начальном этапе рвение просто бешенное, могу несколько дней подряд работать, а потом постепенно желание проподает, вроде видишь что нужно сделать, что исправить, но сука лень какая-то
И вот в такие моменты нужно как-то себя заставить, но как? Как настроить себя на работу?
2 zGidz
А у меня такая же фигня бывает… Только самомотивация и планирование… Всегда должно быть известно какой следующий шаг сделать. А если сидишь и думаешь за что взяться, то в нерабочем настроении это значит, что нифига не будет сделано. Обязательно нужно знать какой следующий шаг сделать, и этот шаг должен быть маленьким — не “исправить все ошибки и написать новый функционал”, а что-то типа “посмотреть какой запрос выполняется при добавлении комментария”. В этом случае мозг понимает что надо сделать чтобы её решить и не боиться её выполнить… Вот так маленькими шажками вся работа делается быстро и весело
А чем отличается ИО тред от воркер треда?
Только логически - характером использования? Или программно он тоже отличается (там правами, еще чем нибудь?)
Я имею ввиду не столько конкретно ваши ИО треды, солько вообще в целом.
ИО тред обрабатывает соединения он один. Воркер-треды выполняют манипуляции над запросами.