Sleep 0 Has Special Meaning

sleep 0 has special meaning?

Yes, for a couple of reasons, first of all, (mri) ruby threads are wrappers around native threads with an additional GVL lock.

Essentially what ruby is doing when you call sleep is calling the underlying, native, platform dependent sleep and releasing the GVL so that other running threads can acquire it. So sleep(0) is both yielding to other native threads that may be waiting to be executed as well as releasing the current thread's hold on the GVL which would otherwise keep the Ruby VM from executing.

Here's a quick rundown of how you can see this from mri source:

  1. We get the definition of Kernel sleep from https://github.com/ruby/ruby/blob/trunk/process.c#L7542, which we see is implemented in c in the function rb_f_sleep
  2. Next we go to rb_f_sleep and see that in the case of a single argument it calls rb_thread_wait_for
  3. Going to the rb_thread_wait_for definition we see a call to sleep_timeval
  4. sleep_timeval has a call to native_sleep
  5. native_sleep is platform dependent and implemented in thread_pthread.c and thread_win32.c for posix and windows systems respectively. In either case we see calls to GVL_UNLOCK_BEGIN here and here

EDIT

To be more precise:

Windows:

The windows implementation of native_sleep uses WaitForMultipleObjects which does indeed yield the remaining time slice, see: Does WaitForSingleObject give up a thread's time slice?

Posix:

The posix implementation uses pthread_cond_timedwait, which blocks the currently running thread.

Either way, the main thing to note here is that Ruby threads use the underlying thread blocking mechanisms of the OS and release the GVL with any call to sleep, allowing other threads to take control.

Significance of Sleep(0)

According to MSDN's documentation for Sleep:

A value of zero causes the thread to
relinquish the remainder of its time
slice to any other thread that is
ready to run. If there are no other
threads ready to run, the function
returns immediately, and the thread
continues execution.

The important thing to realize is that yes, this gives other threads a chance to run, but if there are none ready to run, then your thread continues -- leaving the CPU usage at 100% since something will always be running. If your while loop is just spinning while waiting for some condition, you might want to consider using a synchronization primitive like an event to sleep until the condition is satisfied or sleep for a small amount of time to prevent maxing out the CPU.

What does Thread.Sleep(0) mean C#?

The 0 means there's no minimum period of time before which control will be returned to the thread. If there are any other threads ready to run at the moment, however, they're likely to be scheduled, so your thread will sleep some non-zero period of time.

The same is true with any other time period you specify -- Sleep(N) means it should sleep a minimum of the specified time, but may sleep some arbitrarily greater length of time.

At the same time, Sleep(0) does mean that if there's no other thread ready to run, control can/will be returned to your thread immediately.

Behavior of Python's time.sleep(0) under linux - Does it cause a context switch?

I'd never thought about this, so I wrote this script:

import time

while True:
print "loop"
time.sleep(0.5)

Just as a test. Running this with strace -o isacontextswitch.strace -s512 python test.py gives you this output on the loop:

write(1, "loop\n", 5)                   = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5) = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5) = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5) = 5
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout)
write(1, "loop\n", 5)

select() is a system call, so yes, you are context switching (ok technically a context switch is not actually necessary when you change to kernel space, but if you have other processes running, what you're saying here is that unless you have data ready to read on your file descriptor, other processes can run until then) into the kernel in order to perform this. Interestingly, the delay is in selecting on stdin. This allows python to interrupt your input on events such as ctrl+c input, should they wish, without having to wait for the code to time out - which I think is quite neat.

I should note that the same applies to time.sleep(0) except that the time parameter passed in is {0,0}. And that spin locking is not really ideal for anything but very short delays - multiprocessing and threads provide the ability to wait on event objects.

Edit: So I had a look to see exactly what linux does. The implementation in do_select (fs\select.c) makes this check:

if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
wait = NULL;
timed_out = 1;
}

if (end_time && !timed_out)
slack = select_estimate_accuracy(end_time);

In other words, if an end time is provided and both parameters are zero (!0 = 1 and evaluates to true in C) then the wait is set to NULL and the select is considered timed out. However, that doesn't mean the function returns back to you; it loops over all the file descriptors you have and calls cond_resched, thereby potentially allowing another process to run. In other words, what happens is entirely up to the scheduler; if your process has been hogging CPU time compared to other processes, chances are a context switch will take place. If not, the task you are in (the kernel do_select function) might just carry on until it completes.

I would re-iterate, however, that the best way to be nicer to other processes generally involves using other mechanisms than a spin lock.

Does Sleep(n) with n0 relinquish CPU time to other threads

Yes, zero has the special meaning only in the regard to signal there is no minimal time to wait. Normally it could be interpreted like "I want to sleep for no-time" which doesn't make much sense. It means "I want to give chance to other thread to run."

If it's non-zero, thread is guaranteed not to be returned to for the amount of time specified, of course within the clock resolution. When thread gets suspended it gets a suspended status in the system and is not considered during scheduling. With 0 it doesn't change it's status, so it remains ready to run, and the function might return immediately.

Also, I don't think it is hardware related, this is purely system level thing.

Why some function could reduce sleep(n) into sleep(0)?

As rightly said by Jack, usleep and sleep can be interrupted by the delivery of signals (E.g presence of ioctl, read, write function calls).

One of the smart way to avoid this issue is to use nanosleep. Like sleep and usleep, nanosleep can also be interrupted by the delivery of signals but the difference is, its second argument tells you how much time is remaining.

You can use this argument to make sure your code sleeps for the specified amount of time. Replace your sleep and usleep function with a while loop containing nanosleep. Following is the example usage,

struct timespec to_sleep = { 1, 0 }; // Sleep for 1 second
while ((nanosleep(&to_sleep, &to_sleep) == -1) && (errno == EINTR));

Off course this solutions is not suitable for the application where exact amount sleep is required but it is very useful in the cases where minimum delay is required before executing next function.

Thread.Sleep(0) vs Thread.Sleep(1) vs Thread.Sleep(2)

Thread.Sleep(n); // Where n is milliseconds

When N==0

This tells the system you want to forfeit the rest of the thread’s timeslice and let another waiting thread (whose priority >= currentThread) run (this means you won't be sure when you get your control back).

If there are no other threads of equal priority that are ready to run, execution of the current thread is not suspended.

When N>=1 (be it N=1 or N=2)

Will block the current thread for at least the number of timeslices (or thread quantums) that can occur within n milliseconds, In other words it will relinquish the remainder of its time slice to any other thread un-conditionally.


The Windows thread scheduler ensures that each thread (at least those with the same priority) will get a fair slice of CPU time to execute. The reason for blocking the current thread for atleast specified interval is because scheduler might take longer than the specified interval before it gets around to that thread again.

References: 1, 2, 3


Update

In my pursuit to find the working of Sleep in versions prior to C# 3, I've come across interesting articles (on and before year 2005) that I felt is worth an update.

In short, I did not find any difference with regards to threads relinquishing to higher or same priority when n=1 or n=2.

From Vaults: 1, 2, 3

Why Thread.sleep(0) can prevent gc in rocketmq?

It does not.

Thread's sleep documentation states only:

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors.

What it means it can have side effect on the behaviour of the garbage collector.

By calling Thread.sleep(0), you are (potentially (it's a 0, so implementation could even ignore that)) switching context, and the parallel GC thread could be selected instead, to clean up other references.
The minor side effect is that you potentially run GC more often - what can prevent long-running garbage collections (you are increasing a chance of GC run every 1000 iterations).



Related Topics



Leave a reply



Submit