29

When you sleep a thread, what is actually going on?

I see that sleeping a thread "pauses the current thread for a given period of time". But just how does it work?

According to How Thread.sleep() works internally and How does Thread.sleep really work?:

  • the sleep duration will be subject to some system-specific granularity
  • sleep is blocking
  • the thread leaves the CPU and stops its execution
  • the thread is not consuming CPU time while sleeping

I just can't quite understand the internal and fundamental mechanics of what this all means.

I understand that there is something called the scheduler that is responsible for switching between threads.

Sources seem to indicate that this varies by OS (or hardware?) and most threads are given 1ms - 60ms or so to perform some actions before the CPU switches to another thread.

But when a thread sleeps (for example, many seconds), how does it resume? I'm guessing a timer is involved somehow, is it the motherboard's clock? Is it related to the CPU clock rate?

And even if a timer is involved, how does the CPU know when it's time to pay attention to the thread again? Wouldn't it have to constantly check in on the thread to see if it's ready? Isn't that effectively polling and therefore kind of is consuming CPU time?

Is sleeping a thread language-specific or is the OS responsible for it or is it a CPU-specific thing?

Would someone please explain this to me with basic explanations of things like the scheduler and what the CPU is doing during all of this?

2
  • 2
    Awakening a sleeping thread again works by timed interrupts, typically generated by an interrupt clock, which is a hardware component separate from the core part of CPU which processes the instructions. That avoids the needs for polling. Maybe this explanation on Quora will clarify some things: quora.com/How-does-threading-work-at-CPU-level
    – Doc Brown
    Commented Aug 17, 2016 at 6:38
  • 1
    Most JVMs do not actually implement multi-threading: All they do is use the multi-threading capabilities of the underlying operating system. If you really want to know how it works, there are lots of books on the subject of operating system design. Commented Aug 17, 2016 at 15:15

2 Answers 2

20

There is much more involved in running a program than just the code within that program.

Any program that runs in a multi-process OS is under the control of the OS's scheduler, and the scheduler does maintain an explicit table saying what process is running, which ones are waiting to run when CPU cycles are available, and which are not even trying to run (sleeping). The scheduler typically assigns even-sized time slices to processes depending on their priority and their execution history. Ultimately, this loop is driven by hardware interrupts, usually generated by an oscillator on the main board.

Sleeping is always a feature that a programming language can support only because the run-time environment where it will be executed supports it. A normal program cannot suspend itself, it can only tell the scheduler how it would like to be treated - and the scheduler is by no means obliged or even always capable of satisfying that wish. Think of a laptop being closed and going into hibernation; the main board's oscillator keeps pulsing, but since the scheduler isn't running, no process can be running either no matter how high its priority is.

2
  • 2
    This is (generally) true of processes, but not always true of threads, which are sometimes language dependent. Threads can be implemented independently of the OS, and can be cooperative or preemptive. For example Ruby has "fibers" (in addition to threads). Ruby fibers are cooperatively scheduled.
    – david25272
    Commented Aug 17, 2016 at 23:21
  • 1
    True, only native threads work like this. Green threads are scheduled by the VM executing a program in a byte-compiled language. Usually they are used when native threads are not available, or when running code that isn't properly thread-safe (the VM can sometimes ensure that the semantics of a multi-threaded program remain correct in ways that the OS scheduler cannot). Commented Aug 18, 2016 at 6:14
18

As Doc Brown mentioned in a comment, interrupts are the key, and not just for sleeping.

An interrupt is a hardware signal that the processor should stop what it's doing and run a piece of code. External devices trigger interrupts when they need the processor's attention: for example, when a disk has finished reading data, or a key has been pressed, or a countdown timer on the motherboard has reached zero.

Interrupt-handling code is generally very small and very fast. For example, when the disk indicates a block has been copied to memory, the OS might simply record that fact in a list of "ready blocks" somewhere and then return to whatever else it was doing. You don't want the CPU to be spending all of its time in interrupt-handling code and not running user code.

One piece of interrupt-driven code that isn't necessarily small is the scheduler. It's triggered by a signal from a countdown timer, and examines the state of the system whenever it's run. This will typically include identifying processes that are ready to run (for example, because the block that they were waiting for has arrived in memory), as well as those that have exhausted their time slice.

So, when you execute a thread sleep, what you're doing is telling the OS that (1) you're giving up your time slice, and (2) you shouldn't be awoken again until a certain time has elapsed.

Whenever the scheduler runs, it will look at your thread and only mark it as "ready to run" if that time has elapsed. This is polling, of a sort, but it's not "busy-loop" polling since it's triggered by an interrupt. It's also not that expensive: typically there are only a thousand or so threads running at a time.

This should also give you an idea why sleep times are not exact: when your thread becomes ready to run, there may be other threads that are still running and have not exhausted their time slice. Or there may be higher-priority threads that are ready to run.

4
  • This is the way "modern" operating systems work. In older operating systems, such as Windows 3 or Mac OS prior to OS X a process had to yield() control to the operating system. Unfortunately if a program got stuck in a loop or deadlocked somehow, it might never yield control, and the whole system would hang.
    – david25272
    Commented Aug 19, 2016 at 0:46
  • @david25272 - I think I'd use the word "simpler" rather than "older," as there were systems from the 1960s that did preemptive multitasking. And while it's true that cooperative multtasking requires the active thread to do something to release its control of the processor (typically by making a blocking system call, rather than an explicit yield), I don't believe that there are many general-purpose programmers today who use an OS with a cooperative threading model.
    – kdgregory
    Commented Aug 22, 2016 at 12:05
  • Cooperative multitasking was great. As long as everyone was cooperative.
    – gnasher729
    Commented Sep 6 at 19:56
  • It seems that we're getting back into cooperative multitasking, JavaScript being a prime example but also things like Java virtual threads. Under the covers there's still preemptive multitasking in the OS, of course.
    – kdgregory
    Commented Sep 7 at 13:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.