Является ли Thread.Sleep(1) особенным?
Джо Даффи (автор Параллельное программирование в Windows) пишет в в этой статье в блоге, что Thread.Sleep(1) является предпочтительным по сравнению с Thread.Sleep(0), потому что он будет приостанавливаться для потоков с одним и более низким приоритетом, а не только равными приоритетными потоками, как для Thread.Sleep(0).
. NET-версия MSDN говорит, что Thread.Sleep(0) является специальным, он приостанавливает этот поток и разрешает другие ожидающие потоки выполнить. Но он ничего не говорит о Thread.Sleep(1) (для любой версии .NET).
Итак, Thread.Sleep(1) действительно делает что-то особенное?
Фон:
Я обновляю свои знания о параллельном программировании. Я написал код С#, чтобы наглядно показать, что приращения и декременты pre/post не являются атомарными и, следовательно, не являются потокобезопасными.
Чтобы избежать необходимости создавать сотни потоков, я помещаю Thread.Sleep(0) после увеличения общей переменной, чтобы заставить планировщика запустить другой поток. Эта регулярная замена потоков делает неатомарную природу до/после приращения/уменьшения более очевидной.
Thread.Sleep(0), по-видимому, не вызывает дополнительной задержки, как и ожидалось. Однако, если я изменил это на Thread.Sleep(1), он, похоже, вернется к нормальному режиму сна (например, я получаю примерно минимум 1 мс задержки).
Это будет означать, что хотя Thread.Sleep(1) может быть предпочтительнее, любой код, который использует его в цикле, будет работать намного медленнее.
Этот вопрос SO "Может ли кто-нибудь объяснить это интересное поведение с помощью Sleep (1)?" является своего рода релевантным, но он сосредоточен на С++ и просто повторяет руководство в Статья Джо Дэффи в блоге.
Здесь мой код для всех, кто интересуется (скопирован из LinqPad, поэтому вам может понадобиться добавить класс вокруг него):
int x = 0;
void Main()
{
List<Thread> threadList=new List<Thread>();
Stopwatch sw=new Stopwatch();
for(int i=0; i<20; i++)
{
threadList.Add(new Thread(Go));
threadList[i].Priority=ThreadPriority.Lowest;
}
sw.Start();
foreach (Thread thread in threadList)
{
thread.Start();
}
foreach (Thread thread in threadList)
{
thread.Join();
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Thread.Sleep(200);
Console.WriteLine(x);
}
void Go()
{
for(int i=0;i<10000;i++)
{
x++;
Thread.Sleep(0);
}
}
Ответы
Ответ 1
Вам больше не нужно использовать Sleep(1)
вместо Sleep(0)
, потому что Microsoft изменила реализацию Windows API Sleep()
.
Из документация MSDN для Sleep(), вот что происходит сейчас с Sleep (0):
Значение нуля заставляет поток отбрасывать оставшуюся часть своего временного фрагмента на любой другой поток, который готов к запуску. Если нет других потоков, готовых к запуску, функция немедленно возвращается, и поток продолжает выполнение.
Это то, что раньше происходило в Windows XP:
Значение нуля заставляет поток отбрасывать оставшуюся часть своего временного фрагмента на любой другой поток с равным приоритетом, который готов к запуску. Если нет других потоков равного приоритета, готовых к запуску, функция немедленно возвращается, и поток продолжает выполнение. Это поведение изменилось с Windows Server 2003.
Обратите внимание на разницу между "любым другим потоком" и "любым другим потоком с равным приоритетом".
Единственная причина, по которой Джо Даффи предлагает использовать Sleep (1), а не Sleep (0), - это то, что это самое короткое значение Sleep(), которое не позволит немедленному возврату Sleep(), если нет других потоков с равным приоритетом готовый к запуску при работе в Windows XP.
Вам не нужно беспокоиться об этом для версий ОС после Windows Server 2003 из-за изменения поведения Sleep().
Я обращаю ваше внимание на эту часть блога Джо:
И даже если там явный сон, выдача его не позволяет планировать продюсер, потому что он имеет более низкий приоритет.
В XP потоки с более низким приоритетом будут голодать, даже если основной поток (с более высоким приоритетом) сделал Sleep (0). Post-XP, этого больше не будет, потому что Sleep (0) позволит запускать потоки с более низким приоритетом.