четверг, января 24, 2008

В чем отличие Thread.Sleep() и Thread.SpinWait()

В чем отличие Thread.Sleep() и Thread.SpinWait()? Оба метода позволяют приостановить исполнение текущего потока. MSDN немногословна:
Thread.Sleep
Blocks the current thread for the specified number of milliseconds.

Thread.SpinWait
Causes a thread to wait the number of times defined by the iterations parameter.

После таких описаний, ИМХО, становится еще непонятной.
Между тем различие этих методов весьма велико. Thread.Sleep() не только блокирует текущий поток, но и сообщает планировщику потоков Windows (scheduler) о том, что текущий поток освобождает причитающийся ему квант процессорного времени (time slice), поэтому scheduler может передать этот квант следующему потоку в очереди. Если вы передали в Thread.Sleep() достаточно большое значение, то ваш поток на протяжении всего этого промежутка практически не будет потреблять процессорное время. При обходе очереди потоков, ожидающих своего кванта процессорного времени, планировщик будет просто пропускать ваш поток. Это делает метод Thread.Sleep() весьма полезным, например, для борьбы с явлением известным под названием "инверсия приоритетов", когда потоки с низким приоритетом, но "жадные" до ресурсов, вытесняют потоки с более высоким приоритетом.
Еще один интересный момент. Знаете ли вы, что обозначает вызов Thread.Sleep(0)? Поток вообще не заснет, или передаст управление другому потоку? MSDN говорит:
A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If there are no other threads of equal priority ready to run, the function returns immediately, and the thread continues execution.

Т.е. остаток кванта времени, причитающегося потоку будет отдан другому потоку ожидающему исполнения, с тем-же приоритетом что и у текущего, а если такого потока нет, то текущий поток продолжит работу. Т.е. бороться с инверсией приоритетов при помощи Sleep(0) вообще бессмысленно. Джо Даффи призывает вовсе избегать использования Sleep(0), и даже завести правило в FxCop на этот счет.

Метод Thread.SpinWait() ведет себя совсем по другому. Главное отличие от Thread.Sleep() состоит в том, что здесь не происходит переключение контекста потока. Т.е. текущий поток не передает управление планировщику Windows. Вместо этого, внутри метода SpinWait() запускается некий холостой цикл, на что прозрачно указывает название метода. Число итераций этого передается в параметре метода. Отсюда можно сделать вывод, что время ожидания SpinWait() будет очень незначительным, даже по сравнению с вызовом Sleep(1). Ведь, согласно утверждению Джо Даффи, только переключение контекста потоков занимает 4000+ процессорных тактов.
Для чего стоит применять метод SpinWait()? В основном, в случаях когда потоку необходимо "немножко подождать", не передавая при этом управления другим потокам (понятно, что за время исполнения SpinWait планировщик может прервать текущий поток, но SpinWait этого "не заметит"). Обычно SpinWait() применяют в "тонких" техниках неблокирующих алгоритмов, когда ценой потери нескольких сот тактов мы можем избежать переключения контекста при явной блокировке на мониторе или вызове Sleep(), а также связанных с ними проблем обновления кэша и т.д. Т.е. SpinWait применяется довольно редко. Добавлю, что SpinWait дает хорошие результаты на много процессорных конфигурациях, и еще, его поведение отличается стабильностью и не зависит от аппаратной конфигурации (один процессор, HyperThread, много ядер).

3 комментария:

Анонимный комментирует...

Оффтоп: читаю ваш блог через rss, в последнее время появилось много нового - так держать! Спасибо )

Nick Martyshchenko комментирует...

Хоть мой комментарий и устарел довольно прилично, но все же хотел дополнить: во-первых, MSDN отлично уточнила описания обоих методов, например того же http://msdn.microsoft.com/en-us/library/system.threading.thread.spinwait.aspx в секции Remarks.

А во-вторых, всем заинтересованным порекомендовал бы статью http://msdn.microsoft.com/ru-ru/magazine/cc163427.aspx
где можно познакомиться с отличным примером использования Thread.SpinWait()

Квант комментирует...

У Thread.Sleep() квант процессорного времени 10~12 ms, а у Thread.SpinWait()время ожидания намного меньше. Только вопрос: "Сколько?"