User-land Threads vs Native Threads
Native threads
- Native threads are threads schedule by the kernel, in particular they use the pthread library and have access to the kernel space
- Kernel with the knowledge of underlying OS are aware of native threads
- These threads have full access to the Kernal space
- Kernel requires a full thread control block (TCB) for each thread to maintain information about threads like time slice and the cursor position of the task for the scheduler to resume the execution, they have significant overhead and the context switching is expensive
- Most of the time these threads are specific to the underlying OS implementation and so they are not portable
UserLand Threads
- User-land threads aka User-level threads aka green threads are scheduled by the execution environment like JVM, runtime, etc without relying on any native OS capabilities
- Execution environment and the underlying OS doesn’t recognize these threads
- It has access only to the user space and will not have access to kernel space i.e, the kernel have no knowledge of these threads and treat them like a single process
- User-land threads are pretty lightweight and so the context switching is relatively easy and cheap
- Due to their simple management and representation they are faster and efficient
- These threads are managed by the user program and are not dependent on the scheduler implementation of the underlying OS. So they are portable and can be used in simple kernel, which doesn’t support thread scheduling
Multiprocessing environment
As you can see in the pictures below, native threads can achieves parallelism through multiprocessing CPUs or cores.
User-land threads have scope only inside a single cpu. Modern languages are attempting for a hybrid approach of user-land thread’s concurrency along with native thread’s parallelism.
Context Switching
Native threads can switch between threads pre-emptively, switching control from a running thread to a non-running thread at any time. But this comes with the cost of suspending the state of the thread, and the cost of switching between user mode (for the thread) and kernel mode (for the scheduler).
User-land threads only switch when the control is explicitly given up by a thread or when a thread performs a blocking operation.If a process schedules its own threads directly and cooperatively, the context switch to kernel mode is unnecessary, and switching tasks is quite cheap.
Blocking call
As most of you know, threads have to make system call to perform blocking operations like IO. User-Level threads are not a perfect solution as with everything else, they are a trade off. Since, User-Level threads are invisible to the OS they are not well integrated with the OS. As a result, OS can make poor decisions like scheduling a process with idle threads, blocking a process whose thread initiated an I/O even though the process has other threads that can run and unscheduling a process with a thread holding a lock. Solving this requires communication between between kernel and user-level thread manager.
There is a lack of coordination between threads and operating system kernel. Therefore, process as whole gets one time slice irrespective of whether process has one thread or 1000 threads within. It is up to each thread to relinquish control to other threads.
User-level threads requires non-blocking systems call i.e., a multithreaded kernel. Otherwise, entire process will be blocked in the kernel, even if there are runnable threads left in the processes. For example, if one thread causes a page fault, the process blocks.
Java green threads implementations did not scale over multiple cores or CPUs and was deprecated due to the complexities for the developers to manage the implementation of user space scheduler.
Modern languages like Erlang, Go has the runtime to manage the user level threads and they are a lot better for concurrency and parallelism. Keep in mind that green threads from Java are a lot different from coroutines and coroutines are a lot different from goroutines. This will be a topic for itself to go in depth! Happy Threading!