Некоторые вопросы о многопоточности
Положим, есть приложение, которое работает в рамках одного процесса и является многопоточным.
Вопрос №1: в рамках процесса может быть(а чаще всего и есть) больше потоков, чем есть физически (допустим, что физически у нас 16 потоков, без HT, 16 ядер). Таких программных потоков может быть 100, 200, 1000. Когда я выполняю в рамках программного потока какой-либо системный вызов синхронно,thread.Sleep или, к примеру, autoresetevent.Waitone(в случае ожидания ответа от некоторой внешней среды) , приводит ли это к блокировке и физического потока? Если я запущу параллельно 16 бесконечных thread.Sleep или заблокирую 16 autoresetevent-ов, приведет ли это к блокировке всех 16 физических потоков и зависанию всей ОС? Если ответ на этот вопрос- нет, не приведет, то возникает второй.
Вопрос №2: зачем использовать TAP, EAP или другие шаблоны и подходы, которые во многом предоставляют ту самую возможность не блокировать поток, используя различные конструкции, вроде taskcompletionsource, semaphoreslim, простых коллбэков, если управление потоками на пользовательском уровне позволяет держать множество программных потоков, которые не блокируют физические.
Ответы (2 шт):
Процессор тратит свой ресурс на каждый поток. А еще поток сам по себе захватывает память на стек. Создайте 1000 потоков с бесконечным циклом - узрееете тормоза, так как переключение между потоками - дорогая операция, то при 1000 потоков процессор в основном только и будет делать, что переключаться с одного потока на другой. Однако, тупить будет в основном только ваш процесс, так процессор тратит определенное время на процесс в целом, а не на поток. Отсюда вывод - чем меньше потоков в вашей программе, тем меньше надо переключаться между потоками процессору, тем лучше. То есть меньше потоков = меньше бесполезных переключений = лучше.
Однако, с другой стороны, если процессор поддерживает несколько потоков (или гипертрединг), то вы можете без потерь запускать потоки параллельно, что вас, по идее, ускорит, ведь если потоки работают параллельно, то процессора не надо переключаться. Отсюда вывод - так как поток - ресурс ценный, нужна веская причина что то запускать параллельно. Но если эта причина есть, то можно ускориться.
Теперь смотрим на эти два абзаца и понимаем, что самое эффективное — это запускать необходимый минимум потоков, и каждый из запущенных потоков должен делать максимум работы.
В стародавние времена, когда операции с сетью/файлами/бд были блокирующими, потоки просто ожидали ответа и висели, что является преступной тратой ресурсов памяти и процессора. Например, если у вас вебсайт на 10 потоков, и все они заблокированы базой данных, то 11й запрос на ваш веб сайт не сможет выполнится, даже если ему бд не нужна.
Чтобы такое безобразие прекратить, начали думать как бы не блокировать потоки. И тут добавили EAP. Но вот проблема — это настолько запутывает код, что ЕАР реально использовали ну если уже совсем все плохо.
Думали думали дальше и добавили TAP. Под капотом у него что то вроде старого доброго EAP, но вот программисту это невдомек, программист видит якобы синхронный код. То есть теперь писать неблокирующие операции стало легче, они стали доступнее, и теперь если ваш сайт ждет ответа от бд, он делает это асинхронно, то есть 10 запросов заблокированы, но потоки сервера свободны и могут обслужить 11й запрос.
П.с. Аббревиатуры я мог попутать, набираю с телефона, но смысл я надеюсь понятен.
По вопросу №1: все эти Threadы (нити исполнения) никакой прямой корреляции с физическими ядрами CPU не имеют, так как между программой, которую мы пишем, и CPU стоят такие "посредники" как CLR и ядро ОС. В конечном счете именно ядро ОС раздает кванты времени и определяет какое core CPU использовать в данный конкретный момент времени. Так что нет, Thread.Sleep() физическое ядро CPU не блокирует.
По вопросу №2: все эти EAP, TAP просто этапы эволюции языка C# и способов написания кода, который должен исполняться асинхронно или параллельно.