Очень часто приходится встречать мнение, что заведи в системе побольше нитей — и все будет сразу лучше. Верно ли оно?
Не всегда.
Для начала рассмотрим некий специальный случай, когда ресурсом для какой-то операции является процессор (т.е. операция есть некое длинное вычисление), и оптимизировать хочется именно ее.
Современные машины, конечно, многопроцессорны, и потому загрузить оба процессора — это то же самое, что поставить в магазине вторую кассу — сами понимаете, как упадет очередь и время ожидания в ней.
Вот тут и приходят на помощь нити. Чтобы выполнить что-то на конкретном процессоре — надо запустить нить, и привязать ее к нему по affinity, чтобы она не встала в очередь на первый процессор вслед за первой нитью (в этом случае выигрыша не будет).
Это дает реальный и серьезный выигрыш на вычислительных задачах. Здесь нить не ресурс, а дополнительная сущность, нужная для того, чтобы задействовать ресурс "процессор".
Теперь что касается использования нитей в остальных случаях.
Иногда код зовет длинные блокирующие функции (особенно если речь о вызовах в какие-то сложные чужие компоненты типа DCOM прокси). "Блокирующие" — это такие, в которых может случиться длинное — а, возможно, очень длинное — ожидание.
Ожидает, естественно, одна нить (которая в данном случае есть ресурс). Остальные — не ожидают. В этой ситуации добавление нитей реально повышает скалабильность (при правильном управлении их пулом).
Но есть и другие пути. Если надо работать с диском или сетью — то у нас есть overlapped IO, который позволяет накидать немеряну кучу запросов на разные хэндлы всего одной нитью, не блокируясь. В юниксе есть nonblocking IO, который делает сходное.
Использование этой фичи избавляет от необходимости поддерживать пул нитей, и потому есть благо. Именно из-за nonblocking IO в юниксах годами обходились без нитей.
Если код написан так, что исполняющая его нить не блокируется — то тогда не имеет смысла заводить много нитей.