C # - Когда использовать стандартные потоки, ThreadPool и TPL на сервере высокой активности - PullRequest
28 голосов
/ 13 марта 2011

В последнее время я много читал о многопоточности, поскольку я ищу разработку высокопроизводительного, масштабируемого TCP-сервера, способного обрабатывать до 10 000–20 000 клиентов, каждый из которых последовательно обменивается данными с сервером в двух направлениях командная система. Сервер получит команду и выполнит одну (или несколько) задач в соответствии с командой. Мой вопрос заключается в том, как правильно использовать потоковые конструкции .NET для различных ситуаций, выполняя задачи, которые могут занять от одной минуты до нескольких часов, в зависимости от выполняемой работы.

Больше всего меня смущает тот факт, что везде, где я читаю, я вижу что-то вроде «использования созданного вручную потока (или пользовательского пула потоков) для обработки« долгосрочных »задач и использования TPL для краткосрочных задач, или задачи, которые требуют параллельной обработки. " Что именно является долгосрочной задачей? Это 5 секунд, 60 секунд, час?

С какими временными рамками я должен использовать каждый из этих трех методов создания потоков:

  • Темы, созданные вручную
  • Класс .NET ThreadPool
  • TPL

Другая проблема, которую я рассмотрел, заключается в следующем: скажем, к моему серверу действительно подключено 20 000 клиентов, каждый из которых отправляет 1 команду (которая может выполнять одну или несколько задач) в секунду. Даже при наличии мощного оборудования, не существует ли шанса, что я смогу перенести слишком высокую рабочую нагрузку в любую имеющуюся у меня пул потоков / очередь рабочих элементов, тем самым в конечном итоге сгенерировав исключение OutOfMemoryException после того, как очередь медленно заполнится до максимума?

Любое понимание будет с благодарностью.

Ответы [ 4 ]

18 голосов
/ 13 марта 2011

На самом деле, для этого сценария все из них являются вторичными; первое, на что вы должны обратить внимание, это asyc-IO, он же .BeginRead(...) и т. д .; это позволяет минимизировать количество потоков, ожидая на портах завершения ввода-вывода - гораздо эффективнее.

Как только вы получите полное сообщение, в этом масштабе Я бы выбросил сообщение в пользовательский пул потоков / синхронизированную очередь. У меня будет контролируемое количество обычных потоков (не потоков пула или IOCP), обслуживающих эту очередь для обработки каждого элемента.

Как это происходит, я делаю что-то подобное (более низкая шкала) в данный момент; чтобы предотвратить взрыв памяти, я ограничил рабочую очередь; если он заполнится (т. е. рабочие не могут идти в ногу), то вы можете на короткое время заблокировать IOCP, возможно, с тайм-аутом, который в итоге скажет клиенту «слишком занят» на уровне IOCP.

10 голосов
/ 13 марта 2011

Больше всего меня смущает тот факт, что везде, где я читаю, я вижу что-то вроде «использования созданного вручную потока (или пользовательского пула потоков) для обработки« долгосрочных »задач и использования TPL для короткихживые задачи или задачи, требующие параллельной обработки. "

Странный совет, или, может быть, вы немного ошиблись в цитировании.Поток также способен к параллельной обработке, и с помощью TPL вы можете создать задачу с опцией LongRunning.Остаётся только то, что вы не должны запускать длинные задачи в ThreadPool.

Что такое длительная задача?Это 5 секунд, 60 секунд, час?

TPL запускается поверх ThreadPool, и TP будет создавать новые потоки со скоростью не более 2 в секунду.Такой длительный период работы составляет> = 500 мс


Даже при наличии мощного оборудования, не существует шанса, что я смогу перенести слишком высокую нагрузку в какой-либо пул потоков / очередь рабочих элементовУ меня есть,

Да, никакой инструмент Threading не может увеличить вашу фактическую емкость ...

С 20k-клиентами вам, вероятно, понадобится ферма серверов, возможность включить ее в свой проект на ранней стадии....

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

7 голосов
/ 13 марта 2011

Предложение Марка - это то, как я бы это сделал. Но если задачи выполняются дольше одной секунды, а клиенты отправляют запрос в секунду, очередь будет постоянно увеличиваться.

В этом случае я бы использовал один сервер в качестве фасада, который получает все запросы от клиентов и отправляет ответы на них асинхронно.

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

Другим решением будет использование сервера балансировки нагрузки.

4 голосов
/ 13 марта 2011

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

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

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

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

Если каждая длительная операция ожидает завершения других операций, рассмотрите Windows Workflow Foundation.

Если каждая длинная операция-производительная операция потребляет процессор, вы не хотите, чтобы слишком много из них работали одновременно, иначе это будет перегружать ваш сервер.В этом случае используйте MSMQ и / или TPL для постановки в очередь задач и убедитесь, что одновременно выполняется только несколько из них.

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

...