Pages:
Author

Topic: Могопоточное приложение на C++ (Read 585 times)

newbie
Activity: 35
Merit: 0
Есть полно задач, где многопоточность просто необходима.
Смотря под какие задачи, есть уже много готового софта под различные требования
newbie
Activity: 15
Merit: 0
Спорить о полезности многопоточных приложений - это глупость, когда частота процессоров почти не растет, а ядер полно!
Есть полно задач, где многопоточность просто необходима.

Специализируюсь на написании многопоточных приложений на С/С++ Windows/Linux/FreeBSD
Кому нужно пишите, Telegram @dfservice, другие контакты: https://www.dfservice.com/mail/ru/
member
Activity: 238
Merit: 29
Сейчас бы Boost.ASIO. использовать в 2020 году  Cheesy
legendary
Activity: 2618
Merit: 2304
100 узлов. Допустим, на соединение с узлом и скачивание тратится 15 минут. В одном потоке обработать 100 узлов займёт 25 часов. В 100 потоках 15 минут.

Или 200 узлов. В данном случае в одном потоке это будет 50 часов, в 200 потоках... ты не поверишь... снова 15 минут.

Не факт. Скорость скачивания файла зависит, главным образом, от ширины пропускаемого канала.

На самом деле, создание TCP/IP-соединений и скачивание файлов можно реализовать в одном потоке путём использования неблокирующего режима, когда программа работает с сокетами через события Event. Кстати, иногда веб-серверы ограничивают скорость отдачи данных по каждому TCP/IP-соединению, тогда браузеры пытаются создать дополнительные соединения и скачивать файлы с директивой Range в HTTP-заголовках, но веб-серверы, в зависимости от настроек, обычно отвергают такие множественные соединения для обращения к одному ресурсу.

Вообще, описанная схема со множеством потоков, наверно, более актуальна для отправки ICMP-пакетов, когда необходимо быстро проверять узлы сети на доступность, пингуя большое количество IP-адресов.
legendary
Activity: 3108
Merit: 1359
100 узлов. Допустим, на соединение с узлом и скачивание тратится 15 минут. В одном потоке обработать 100 узлов займёт 25 часов. В 100 потоках 15 минут.
Не допустим. Если разработчик не умеет в неблокирующие сокеты, то ему надо не писать многопоточные приложения, а увольняться.  Roll Eyes

P.S. Скачивание файлов - это вообще бенчмарк для сети и диска, а никак не для процессора. Упереться в процессор на этой задаче нужно ещё постараться.
sr. member
Activity: 1316
Merit: 420
KTO EC/\U HUKTO?
Примерно в 100% случаев, многопоточность делает приложению только хуже.

Почему же, при работе, например, с сетями многопоточность рулит. То ли последовательно обрабатывать 100 подключений за 100 раз (соединение, запрос, запись ответа, рассоединение, следующий узел пока до сотого не дойдём), то ли многопоточно 100 соединений за 1 раз (запуск сразу 100 потоков, соединение, ...., рассоединение, готово!). Причём многопоточность будет выигрывать тем раз больше, чем больше количество подключений нужно обработать. А если учесть затупы связи, то это уже не преимущество даже, а необходимость.
Всё описанное не имеет никакого отношения к многопоточности и прекрасно работает в одном потоке.
Если кому-то из программистов для такого необходима многопоточность, то ему показана хирургическая коррекция расположения рук.

[_IMG]https://img.techpowerup.org/200214/8579688.jpg[/img]

100 узлов. Допустим, на соединение с узлом и скачивание тратится 15 минут. В одном потоке обработать 100 узлов займёт 25 часов. В 100 потоках 15 минут.

Или 200 узлов. В данном случае в одном потоке это будет 50 часов, в 200 потоках... ты не поверишь... снова 15 минут.

100-200 параллельных соединений без проблем обрабатываются в одном потоке. Зачем тебе отдельный поток для каждого соединения?
legendary
Activity: 2044
Merit: 1231
Примерно в 100% случаев, многопоточность делает приложению только хуже.

Почему же, при работе, например, с сетями многопоточность рулит. То ли последовательно обрабатывать 100 подключений за 100 раз (соединение, запрос, запись ответа, рассоединение, следующий узел пока до сотого не дойдём), то ли многопоточно 100 соединений за 1 раз (запуск сразу 100 потоков, соединение, ...., рассоединение, готово!). Причём многопоточность будет выигрывать тем раз больше, чем больше количество подключений нужно обработать. А если учесть затупы связи, то это уже не преимущество даже, а необходимость.
Всё описанное не имеет никакого отношения к многопоточности и прекрасно работает в одном потоке.
Если кому-то из программистов для такого необходима многопоточность, то ему показана хирургическая коррекция расположения рук.

[_IMG]https://img.techpowerup.org/200214/8579688.jpg[/img]

100 узлов. Допустим, на соединение с узлом и скачивание тратится 15 минут. В одном потоке обработать 100 узлов займёт 25 часов. В 100 потоках 15 минут.

Или 200 узлов. В данном случае в одном потоке это будет 50 часов, в 200 потоках... ты не поверишь... снова 15 минут.
kzv
legendary
Activity: 1722
Merit: 1285
OpenTrade - Open Source Cryptocurrency Exchange
Почему же, при работе, например, с сетями многопоточность рулит. То ли последовательно обрабатывать 100 подключений за 100 раз (соединение, запрос, запись ответа, рассоединение, следующий узел пока до сотого не дойдём), то ли многопоточно 100 соединений за 1 раз (запуск сразу 100 потоков, соединение, ...., рассоединение, готово!). Причём многопоточность будет выигрывать тем раз больше, чем больше количество подключений нужно обработать. А если учесть затупы связи, то это уже не преимущество даже, а необходимость.

Как я уже пару раз тут говорил: сервер это одна из очень немногих специфичных задач, которая легко распараллеливается. В большинстве реальных задач распараллеливание невозможно.
Конкретно касаемо сервера, как я уже тут упоминал, имеет практический смысл делать число потоков не более чем есть ядер в процессоре. Если количество потоков будет больше, то параллельность будет сугубо мнимой. Чудес не бывает.
Все это касается случая когда поток это поток, а не процесс. Процессы можно раскидать не только по ядрам одного процессора, но и по нескольким отдельным процессорам и/или по нескольким отдельным машинам.
legendary
Activity: 3108
Merit: 1359
Примерно в 100% случаев, многопоточность делает приложению только хуже.

Почему же, при работе, например, с сетями многопоточность рулит. То ли последовательно обрабатывать 100 подключений за 100 раз (соединение, запрос, запись ответа, рассоединение, следующий узел пока до сотого не дойдём), то ли многопоточно 100 соединений за 1 раз (запуск сразу 100 потоков, соединение, ...., рассоединение, готово!). Причём многопоточность будет выигрывать тем раз больше, чем больше количество подключений нужно обработать. А если учесть затупы связи, то это уже не преимущество даже, а необходимость.
Всё описанное не имеет никакого отношения к многопоточности и прекрасно работает в одном потоке.
Если кому-то из программистов для такого необходима многопоточность, то ему показана хирургическая коррекция расположения рук.

legendary
Activity: 2044
Merit: 1231
Примерно в 100% случаев, многопоточность делает приложению только хуже.

Почему же, при работе, например, с сетями многопоточность рулит. То ли последовательно обрабатывать 100 подключений за 100 раз (соединение, запрос, запись ответа, рассоединение, следующий узел пока до сотого не дойдём), то ли многопоточно 100 соединений за 1 раз (запуск сразу 100 потоков, соединение, ...., рассоединение, готово!). Причём многопоточность будет выигрывать тем раз больше, чем больше количество подключений нужно обработать. А если учесть затупы связи, то это уже не преимущество даже, а необходимость.
legendary
Activity: 3108
Merit: 1359
К счастью у меня нет готового примера, хотя... Можно глянуть на разработчиков апача наверное.
Разработчики Microsoft IIS, и даже получилось неплохо. Правда, там в центре вселенной драйвер http.sys, который в режиме ядра работает, т.е. вообще вся работа вебсервера с сокетами и большая часть i/o обходятся без переключений контекста, что немного жульничество.

Решили вопрос в лоб, так сказать.

P.S. Результаты не сферические, говорю как тот кто с IIS и nginx достаточно большое время просовокуплялся.
kzv
legendary
Activity: 1722
Merit: 1285
OpenTrade - Open Source Cryptocurrency Exchange
один рабочий процесс nginx обслуживает множество подключений одновременно,
Правильно

а в апаче только одно,
Правильно

и если nginx будет использовать классические потоки (то же самое что threads в винде), с точки зрения производительности не изменится абсолютно ничего

Если нам договориться между собой тут, что потоки и процессы это одно и тоже, то для nginx очевидно ничего не поменяется ни в плане производительности, ни в каком другом плане. С чего бы вдруг?
Что-то поменяется если вы вместо асинхронной однопоточной работы начнете внутри процесса nginx плодить потоки для каждого сокета как это сделано в апаче. Вот тогда и увидите ад как он есть!

Остался еще один шаг: что по вашему делает "мастер процесс"?
Если говорить про сеть, то раньше он висел на приеме входящих соединений, с появлением опции reuseport это тоже переехало в worker-ы, так что теперь мастер просто читает конфиг и управляет worker-ами.
Ну вот мы еще раз убеждаетмся, что "потоки" в nginx это никакие не потоки, а самые настоящие процессы. Каждый "поток" это отдельный самостоятельный экземпляр сервера. Вся "многопоточность" нгинкса заключается в клонировании единственного однопоточного процесса.
То есть если на примере стартового поста темы, то автору многопоточность в стиле nginx можно организовать очень просто: достаточно сказать слушающему сокету "O_REUSEPORT" и запустить несколько экземпляров демона коина.

Можно... Если хотите чтобы все встало колом из-за ваших потоков и опросов
Это с каких пор epoll колом встает из-за его вызова из нескольких потоков? Может тесты какие подтверждающие есть? Smiley Это у kqueue такие проблемы есть и там рекомендуют использовать несколько независимых экземпляров для цикла сообщений, а у epoll и виндового iocp всегда все ок с этим было.
Мы обсуждаем сферического коня какого-то. Давайте ваш многопоточный сервер уже посмотрим.

Но ничто не мешает асинхронному приложению быть многопоточным

Если договориться, что потоки и процессы это одно и тоже, то конечно.
newbie
Activity: 28
Merit: 6
Не совсем верно. Термин масштабирование означает увеличение производительности при добавлении ресурсов (обычно аппаратных, но не всегда).
Да, я просто частный случай упомянул, и то не совсем корректно, увеличение тактовой частоты (т.е. обычный разгон) ведь тоже увеличение мощности железа Smiley

Ну тогда сортировка пузырьком тоже масштабируется...
Какой смысл обсуждать масштабирование с ростом частоты, понятно, что тут почти все масштабируется Smiley Да только уже давно частоты CPU уже давно не растут...
Предлагаю вернуться к ядрам CPU, тут сортировка пузырьком никак не масштабируется, но есть работающие алгоритмы параллельной сортировки, в редких случаях бывают полезны.

ЗЫ: я тут на досуге документацию почитал, все таки я спрашивал про многопоточность в нгинкс, а вы переводите стрелки на процессы. Надеюсь вы в курсе, что потоки и процессы это разные вещи?
Это зависит от конкретной ОС, в винде это принципиально разные объекты, потоки одного процесса имеют только свой собственный контекст исполнения (регистры, стек, TLS и все что туда входит), все потоки одного процесса имеют общее адресное пространство, файловые дескрипторы, и.т.д.
В линуксе нет такой четкой границы между потоком и процессом, например 2 таких объекта могут иметь разные идентификаторы (тогда падение одного из них не затронет остальные), но работать в одном адресном пространстве, а может быть наоборот (см. параметры системного вызова clone).
Рабочие процессы в nginx в линуксе имеют отдельные идентификаторы, но предполагаю, что они имеют общее пространство файловых дескрипторов (иначе не очень понимаю, как реализивать опцию reuseport). Теперь по сути, вы называете nginx однопоточным и асинхронным, а апач синхронным многопоточным, реально же их рабочие процессы по сути одни и теже сущности в ОС (пусть будут процессы), только один рабочий процесс nginx обслуживает множество подключений одновременно, а в апаче только одно, и если nginx будет использовать классические потоки (то же самое что threads в винде), с точки зрения производительности не изменится абсолютно ничего, стабильность только пострадает.

По умолчанию поддержка многопоточности выключена (http://nginx.org/ru/docs/http/ngx_http_core_module.html#aio)
Это уже совсем другая настройка, так включается пул потоков для запуска синхронных (блокирющих) операций без блокировки цикла сообщений. В зависимости от типа нагрузки опция может поднять производительность на тысячи процентов, а может и ничего не дать.

Остался еще один шаг: что по вашему делает "мастер процесс"?
Если говорить про сеть, то раньше он висел на приеме входящих соединений, с появлением опции reuseport это тоже переехало в worker-ы, так что теперь мастер просто читает конфиг и управляет worker-ами.

Можно... Если хотите чтобы все встало колом из-за ваших потоков и опросов
Это с каких пор epoll колом встает из-за его вызова из нескольких потоков? Может тесты какие подтверждающие есть? Smiley Это у kqueue такие проблемы есть и там рекомендуют использовать несколько независимых экземпляров для цикла сообщений, а у epoll и виндового iocp всегда все ок с этим было.

Хотите увидеть пример программиста, который решил сделать многопоточное приложение потому что так проще и якобы эффективнее, а в итоге сломал себе весь мозг костылями из нагромождений локфри структур и получил через год то же самое, что другой программист сделал в одном потоке за неделю?
К счастью у меня нет готового примера, хотя... Можно глянуть на разработчиков апача наверное.
А чего обсуждать криворуких программистов, да многопоточность это сложно и требования к квалификации разработчиков намного выше.

---

Что-то перестал понимать что мы тут обсуждаем, поэтому хочу вернуться к мысли, которую хотел донести еще в первом сообщении, многопоточность и асинхронности никак не связаны между собой, первое про количество одновременно исполняемых потоков, второе про архитектуру, и, естесственно, асинхронные приложения более эффективно используют ресурсы. Но ничто не мешает асинхронному приложению быть многопоточным - запускать цикл сообщений одновременно в нескольких потоках, использовать отдельные потоки для тяжелых вычислений, или комбинировать все это, получая прирост производительности.
legendary
Activity: 2618
Merit: 2304
На одном ядре приложение захлебывается. В настоящее время самый оптимальный пусть масштабирования - это многопоточность (что бы потом можно было масштабировать производительность путем наращивания процессорных мощностей). Но столкнулись с проблемой при переходе на многопоточный режим ASIO.

Полагаю, что разбор Вашего кода новым программистом, пусть даже "скиллованным" в области имплементации "Boost.ASIO", займёт ещё больше времени. Скорее всего, для перевода приложения на многопоточную архитектуру потребуется переписать всю Вашу программу на C++ с добавлением нового объекта "boost::asio::io_context io".
Code:
#include

Честно говоря, не знаю, какие могут быть проблемы с реализацией многопоточности конкретно в Вашем случае. Документация "Boost.ASIO", описывающая использование таймеров синхронно и асинхронно, а также работу хэндлеров, в целом, ясная и понятная:
https://www.boost.org/doc/libs/1_72_0/doc/html/boost_asio/tutorial.html
kzv
legendary
Activity: 1722
Merit: 1285
OpenTrade - Open Source Cryptocurrency Exchange
Сам термин масштабирование означает увеличение производительности при увеличении мощности железа Smiley

Ну тогда сортировка пузырьком тоже масштабируется...
Я вообще тогда не знаю - что не масштабируется  Grin

Но вы же пишете
nginx запускает один мастер процесс и N рабочих процессов (параметр worker_processes в конфиге), каждый из которых мониторит события на своем наборе сокетов, подключения между рабочими процессами распределяются равномерно. Да, можно сделать 1 рабочий процесс и задействовать одно ядро, а можно выставить их по количеству ядер и задействовать CPU полностью, получив при этом почти линейное масштабирование по количеству ядер, что тут может быть не понятного? Smiley

Итак зафиксируем:
1. Один поток процесс работает сразу с большим набором сокетов.
2. Можно сделать несколько потоков процессов, которые никак друг с другом не взаимодействуют. Каждый поток процесс работает независимо со своим набором сокетов.

ЗЫ: я тут на досуге документацию почитал, все таки я спрашивал про многопоточность в нгинкс, а вы переводите стрелки на процессы. Надеюсь вы в курсе, что потоки и процессы это разные вещи?

По умолчанию поддержка многопоточности выключена

Остался еще один шаг: что по вашему делает "мастер процесс"?

А еще можно использовать один файловый дескриптор epoll для всех подключений и опрашивать его из нескольких потоков, так получим более равномерное распределение нагрузки по потокам.

Можно... Если хотите чтобы все встало колом из-за ваших потоков и опросов Grin

Хотелось бы увидеть на примере, как lock-free контейнеры приводят к однопоточному приложению, если они предназначены для доступа на чтение/запись из нескольких потоков Smiley

Хотите увидеть пример программиста, который решил сделать многопоточное приложение потому что так проще и якобы эффективнее, а в итоге сломал себе весь мозг костылями из нагромождений локфри структур и получил через год то же самое, что другой программист сделал в одном потоке за неделю?
К счастью у меня нет готового примера, хотя... Можно глянуть на разработчиков апача наверное.
sr. member
Activity: 1316
Merit: 420
KTO EC/\U HUKTO?
Масштабироваться будет, но скорости это не прибавит, а скорее наоборот... Вот такая загогулина ))
Это вообще как  Grin
Сам термин масштабирование означает увеличение производительности при увеличении мощности железа Smiley

Не совсем верно. Термин масштабирование означает увеличение производительности при добавлении ресурсов (обычно аппаратных, но не всегда).
newbie
Activity: 28
Merit: 6
Масштабироваться будет, но скорости это не прибавит, а скорее наоборот... Вот такая загогулина ))
Это вообще как  Grin
Сам термин масштабирование означает увеличение производительности при увеличении мощности железа Smiley

Попробуйте ответить на следующий вопрос: если нгинкс может использовать для работы всего один поток, то как он это делает?
Когда поймете ответ, тогда подумайте над следующим вопросом: что делают дополнительные потоки нгинкса?
nginx запускает один мастер процесс и N рабочих процессов (параметр worker_processes в конфиге), каждый из которых мониторит события на своем наборе сокетов, подключения между рабочими процессами распределяются равномерно. Да, можно сделать 1 рабочий процесс и задействовать одно ядро, а можно выставить их по количеству ядер и задействовать CPU полностью, получив при этом почти линейное масштабирование по количеству ядер, что тут может быть не понятного? Smiley
А еще можно использовать один файловый дескриптор epoll для всех подключений и опрашивать его из нескольких потоков, так получим более равномерное распределение нагрузки по потокам.

На винде нет ни poll ни тем более epoll. Последний и не на всех линуксах есть. Так что libevent использует то, что есть в системе: select в винде, а в других - по обстоятельствам...
Я же написал про винду и iocp, разве нет?
Poll есть на винде по немного другим названием WSAPoll https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll (начиная с висты)
libevent использует select, в винде  потому что использовать iocp не выйдет, другая архитектура https://en.wikipedia.org/wiki/Proactor_pattern, тут, думаю, тоже должно быть понятно.
boost asio может задействовать iocp, но он сам по себе тяжеловесный и для high load не очень рекомендуется.

И мы вернемся к асинхронному однопоточному приложению ))
Хотелось бы увидеть на примере, как lock-free контейнеры приводят к однопоточному приложению, если они предназначены для доступа на чтение/запись из нескольких потоков Smiley
kzv
legendary
Activity: 1722
Merit: 1285
OpenTrade - Open Source Cryptocurrency Exchange

Не совсем так, внутри libevent использует epoll на Linux и kqueue на MacOS X/FreeBSD, отличия от select есть, и довольно заметные.
При использовании select или poll на вход этой функции передается список файловых дескрипторов, после того, как функция возвращает управление, вызывающий обязан в цикле перебрать все эти дескрипторы и проверить их состояние.

На винде нет ни poll ни тем более epoll. Последний и не на всех линуксах есть. Так что libevent использует то, что есть в системе: select в винде, а в других - по обстоятельствам...

Nginx использует фиксированное количество потоков (но никак не один, если это специально не указать в конфиге)

Попробуйте ответить на следующий вопрос: если нгинкс может использовать для работы всего один поток, то как он это делает?
Когда поймете ответ, тогда подумайте над следующим вопросом: что делают дополнительные потоки нгинкса?

А если делать по уму, и синхронизировать только передачу данных между потоками, то приложение будет масштабироваться.

Масштабироваться будет, но скорости это не прибавит, а скорее наоборот... Вот такая загогулина ))

Если есть высокие требования к производительности, то можно внедрить lock-free контейнеры...

И мы вернемся к асинхронному однопоточному приложению ))
newbie
Activity: 28
Merit: 6
libevent это библиотека которая внутри использует тот же select или poll. Библиотека сама по себе не делает код синхронным или асинхронным, все зависит от того, кто эту библиотеку и как применяет.
Не совсем так, внутри libevent использует epoll на Linux и kqueue на MacOS X/FreeBSD, отличия от select есть, и довольно заметные.
При использовании select или poll на вход этой функции передается список файловых дескрипторов, после того, как функция возвращает управление, вызывающий обязан в цикле перебрать все эти дескрипторы и проверить их состояние. Т.е. если к серверу подключены 10000 клиентов, то в ответ на приходящие данные хотя бы от одного из 10000 клиентов сервер будет проверять состояние всех 10000 подключений, алгоритмическая сложность O(n). epoll и kqueue возвращают список именно тех файловых дескрипторов, состояние которых изменилось, получаем алг.сложность O(1).
Кроме того, между select и poll есть различия, select использует номер файлового дескриптора как индекс в массиве, и если у тебя открыто более 1024 файлов (даже не сокетов, просто обычных файлов достаточно), то приложение, использующее select просто упадет.
Также надо упомянуть и винду, ее API ввода/вывода использует "порты завершения" (IOCP и чуть более оптимизированную реализация с названием RIO начиная с 8.1), поэтому libevent вынужден использовать select под виндой и не может эффективно работать.. nginx это тоже касается Smiley

Однопоточный это и есть асинхронный в моем понимании.
Ну вот опять Smiley

Есть два подхода к написанию сервера, условно "как нгинкс" и "как апач".
Апач создает новый поток на каждое подключение и захлебывается, когда количество подключений приближается к 1000 или еще раньше. Nginx использует фиксированное количество потоков (но никак не один, если это специально не указать в конфиге), которые работают в цикле сообщений, используя наиболее эффективный механизм для ОС (см. про epoll и kqueue выше).

На практике, чтобы организовать межпоточное взаимодействие, придется расчехлять блокировки вроде мютексов и в результате код обретет кучу ненужных костылей, а производительность не улучшится, а скорее всего станет хуже по сравнению с асинхронным, однопоточным подходом..
Если блокироваться на мьютексе по приходу данных, как делает Bitcoin Core (да и наверное все остальные клиенты Smiley ) и отпускать блокировку по окончания обработки этих данных, то, безусловно, да Smiley А если делать по уму, и синхронизировать только передачу данных между потоками, то приложение будет масштабироваться. Если есть высокие требования к производительности, то можно внедрить lock-free контейнеры... только убедиться, что в данном конкретном случае они дают выигрыш производительности Smiley
kzv
legendary
Activity: 1722
Merit: 1285
OpenTrade - Open Source Cryptocurrency Exchange

Код для работы с сетью в bitcoin core вполне себе асинхронный и всегда им был (раньше использовал системный вызов select, сейчас вроде как libevent, хотя на 100% не уверен), но оптимизированным его назвать сложно, т.к. он при обработке сообщения захватывает мьютекс cs_main, т.е. фактически однопоточный.


libevent это библиотека которая внутри использует тот же select или poll. Библиотека сама по себе не делает код синхронным или асинхронным, все зависит от того, кто эту библиотеку и как применяет.

Однопоточный это и есть асинхронный в моем понимании.
Синхронный это многопоточный, который для межпоточного общения использует блокировки.

Есть два подхода к написанию сервера, условно "как нгинкс" и "как апач".
Первый это когда после выхода из select (или poll) для каждого сокета создается отдельный контекст в главном потоке, а второй - когда каждый сокет отправляется в отдельный поток. Вот этот второй способ, он на первый взгляд кажется простым и естественным, но это только на первый взгляд. На практике, чтобы организовать межпоточное взаимодействие, придется расчехлять блокировки вроде мютексов и в результате код обретет кучу ненужных костылей, а производительность не улучшится, а скорее всего станет хуже по сравнению с асинхронным, однопоточным подходом.
Pages:
Jump to: