Как правильно организовать вечный цикл в отдельном потоке C++11?
Доброго времени суток.
Есть задача постоянно слушать сокеты, ну и что-то с этим всем делать, класс назван Transport.
Т.к. типы сокетов бывают разные + в конечном классе совсем не надо показывать внутренности - сделал через pimpl.
Теперь дальше, в основной программе крутится вечный цикл, и в начале каждой итерации происходит обращение к моему классу, там вызывается epoll_wait. Для того, чтобы епол не блокировал исполнение вечного цикла, параметр таймаута выставил в 0.
Хочу сделать следующее, чтобы в основной программе не вызывать постоянно функцию Transport, вынести последний в отдельный поток со своими вечным циклом. И добавить 2 очереди, одна на прием, вторая на отправку. И они должны быть обе видны из основной программы, т.е. в начале итерации цикла, программа смотрит что есть в очереди на прием, вычитывает(вынимает) сообщения, дальше в цикле где то записывает информацию в очередь на отправку. Transport в свою очередь в вечном цикле опрашивает епол, если что-то вычитал, записывает в очередь на прием и постоянно просматривает очередь на отправку, если не пустая - отправляем. Вопрос в следующем, с потоками на c++11 не работал, есть опыт только с Qt-потоками, поэтому прошу вашей помощи. Лучше весь класс запихать в поток, если только его вечный цикл? Если только цикл, как будет выглядить доступ из потока до очередей? Если весь класс, то метода положить в очередь, и получить из очереди обворачивать в мьютексы?
Ну и, как я понял, при создании потока, надо сразу сделать detach?
Заранее благодарен.
А зачем вам тогда epoll? Чем он, в вашем случае, будет отличаться от обычного блокирующего сокета в отдельном потоке?
Его смысл как раз в асинхронности и неблокируемости, а вы эти плюшки сводите на нет, вводите тормозящие мьютексы, очереди.
Раз уж пишете на плюсах асинхронно, то возьмите библиотеку для этого, тот же Asio.
Не могу я взять Asio, есть определенные ограничения. Епол был реализован совсем не давно, и при этом в самом начале я столкнулся с такой проблемой, что если поставить для таймаута еполлу -1 - тогда в основной программе вечный цикл блокируется и ждет. И моя задача, разделить основной цикл программы от еполла. Собственно ради этого всё и делается сейчас, чтобы еполл мог работать сам по себе: ждать пару типов сообщений, один тип должен обрабатывать сам, другой тип должен каким-то образом передать основной программе.
Тогда у вас архитектура какая-то кашеобразная, либо вы используете epoll там, где он не нужен.
один тип должен обрабатывать сам
Как epoll может обрабатывать сообщения, если его задача их просто принимать/доставлять? Разделите обязанности, в этом классе максимум должна быть стейтмашина для принятия асинхронных пакетов и сборки их в конкретное сообщение. Дальше уже, когда сообщение готово, передавайте его куда угодно, хоть в другой тред, хоть в другое приложение.
Просто, не понятно, зачем вам тогда epoll, если у вас блокируется тред? Это же только проблемы лишние создаст и усложнит код, почему бы тогда обычные блокирующие сокеты не использовать, они ж в разы проще, а выхлоп тот же...
Дмитрий, вы копаете не в ту сторону. Я не говорил, что у меня еполл что-либо обрабатывает. Я говорил, что мой класс Транспорт содержит в себе механизм как еполл, так и обработки сообщений. Какой тред у меня блокируется? О чем вы говорите? Вопрос мой был совершенно про другое - как правильно организовать второй поток: засунув в поток весь объект класса или только функцию с вечным циклом? В первом случае, если объект класса будет в потоке, смогу ли я дёргать открытые функции объекта? P.S. не заметил особой разницы в трудозатратах между select и epoll.
, разве вечный цикл не подразумевает блокировку треда?
P.S. не заметил особой разницы в трудозатратах между select и epoll.
Вот об этом я и говорю, в общем то смысле. Если вы не заметили, зачем вам нужен epoll, значит вы, вероятно, его используете не для того, для чего он предназначен, а это явный оверкилл. Как я понимаю, вы epoll'ом только ждёте коннекта, а не мультиплексируете IO в широком смысле, не используете кастомные ивенты, и т. п. для чего он предназначен, собственно, если это так, то ту же задачу исполнят блокирующие сокеты с меньшими усилиями. Если я не правильно вас понял, то опишите подробнее, что у вас за архитектура и зачем вам в блокирующемся треде публичные методы...
Я это понимаю как:
1. Висит транспорт-поток заблокированный, ждет данных с сокета.
2. Ивент пришёл, вы прочитали.
2.1 Если у вас стримминг, распарсили заранее заинжекченым парсером, решили нужны ли ещё данные для завершения парсинга, если нужны, ждёте дальше. Пришло — приняли, собрали сообщение.
2.2 Если у вас датаграммы, всё ваще втупую принялось за один вызов и готово.
3.1 Если вам важно реалтаймово обработать сообщение, дёрнули заранее подписанный на сообщение обсервер. Обработали сообщение в том же потоке, соответственно. Для этого можно заранее создать пул из необходимого количества тредов, и ждать в каждом треде одни и те же дескрипторы. ОС сама раскидает запросы по разным тредам и вам не придётся думать что где обрабатывать.
3.2 Если можно повременить с обработкой, запушили куда-то в очередь, в кольцевой буфер или ещё куда-то, куда нравится. Кинули ивент о том, что готово новое сообщение. Обработали готовое сообщение в любом другом потоке.
А проблема select'а в том, что он мониторит весь список дескрипторов. Т. е. если у вас 10-20 тысяч подключений висит, и каждое из них мультиплексит select, то вам приходится гонять целый массив дескрипторов в ядро, там его обходить, проверять ивенты и изменения, и гнать результат обратно в юзерспейс, и так происходит каждый его вызов. На десятке дескрипторов, конечно, вы ничего не заметите, а с десятками тысяч всё просто ложится...
Дмитрий: попытаюсь более подробно рассказать. Сразу предупрежу, что всё, что я сейчас делаю, для меня новое (select, epoll, thread), и изучаю по мере продвижения. Основная программа крутит у себя вечный цикл и помимо работы с сетью исполняет еще кучу задач. Изначально, у меня было всё на select'ах. На каждой итерации цикла я лез в класс-транспорт, и вызывал функцию селекта, пробегал по всем сокетам, смотрел есть ли данные. И вроде всё было хорошо, пока не столкнулся с такой проблемой, что если один сокет внезапно обрывается, я не нашел как из FD_ISSET вытащить именно оборвавшийся чтобы корректно его закрыть. Чтобы не сильно тормозить разработку, почитал сначала про poll, ну и в интернетах везде писали, что poll конечно хорош, но вот epoll это прям вообще бомба. Ну и думаю, а дай его попробую Он уже и показывал какой сокет оборвался, корректно его завершаю, программа не выдаёт кучу ошибок. В общем мне понравилось всё, за исключением того, что пока ведется разработка нагрузки нет, и если поставить epoll'у таймаут -1, то у меня цикл в основной программе дальше не идёт. Сначала сделал таймаут = 0, и так же, как в случае с select'ом на каждой итерации дергал функцию. Мне не нравится такой подход, к тому же этот класс транспорт будет использован в других программах компании, и надо сделать прям всё правильно. Начал копать в сторону тредов. Вынести обработку epoll'а в отдельную нитку, которая была бы не связана с основным циклом. Этот цикл, по задумке, должен на каждой итерации просто смотреть, а есть ли в очереди что-то для него? если нет, то дальше исполняем, если есть то всё вычитываем. Основная программа, назовем её логика, а сообщения для неё - логическими. А есть еще сервисные сообщения, которые должны обрабатываться самим транспортом, это служебные сообщения. И вот, помимо логических сообщений, гоняются сервисные, которые вообще никак не связаны с логикой, и не должны зависеть от состояний логики, начала она новую итерацию или нет. Поэтому, мне показалось что класс транспорт очень хорошо должен лечь в поток. У транспорта, я предполагаю, будут две очереди: queueInput, queueOutput. Как только пришло сообщение, транспорт его прочитал, понял что тип сообщения - логический, кладёт его в queueInput. Логика в свою очередь, во время своей работы если надо отправить сообщений, кладёт своё логическое сообщение в queueOutput. И транспорт, периодически просматривая queueOutput забирает оттуда сообщение и отправляет в сеть. Клиентов вообще немного, но предполагается сообщения будут ходить постоянно. Если сервер не видит активности от клиента (передача каких либо сообщений), то посылает ему некое сервисное сообщения а-ля ты живой? Вот, это все мои рассуждения на данном этапе, и исходя из этого тред-пулл мне кажется здесь избыточен. Можно было бы делать всё на Qt, я бы не задумывался и делал сигнал-слоты и всё бы работало. Но нужен чистый c++11. И многого я еще не знаю, поэтому прошу сильно не ругаться. Кстати, большое спасибо за помощь. :-) На данный момент вообще, всё что я рассматриваю это Unix Domain Socket, но транспорт надо будет так же расширить до UDP. То есть сделать универсальный класс, который, как я вижу, в конечной программе надо создать объект этого класса, и не задумываться как он работает. Грубо говоря у класса-транспорта открыты 2 метода: просмотреть\вычитать очередь и положить в очередь. Парсинг есть, сообщения разбираются отлично. Вопрос вот только остаётся как организовать правильную паралельную работу транспорта и логики.