Threading

Hello everybody, long time no post. My posts have been sporadic at best lately, but hopefully, this one will gain some traction and be helpful to many who find this thread.

Okay, I will give a little preamble, this may be bloat and you can skip it, although it will give you a better understanding of my knowledge or lack thereof on the subject. I'm reading "Modern Operating Systems 4th edition" by Tananbuam(creator of MINIX) and Herbert Bos. Chapter 2 which I am currently reading focuses on processes and threads(both user-level and kernel-level threads).

To summarise a process as quickly as possible; a process is a program in execution managed by the operating system. The OS keeps a process table which is an array of structures, one structure per process, these structures are generally known as the processes PCB(process control block). The PCB for each process keeps vital information about that process such as its registers(pc, stack pointer, etc,), stack, PID, it's resources, and pointers to memory(text section, stack, dynamically allocated memory, etc). The operating system implements a scheduler which gives each process a time quantum to use the CPU. A process can be in one of 3 main states: running, ready and blocked. The scheduler will choose a ready process for the CPU to run(now running). For the aforementioned ready process to transition to a running process, the OS must do a context shift and save all of the (now suspended) process's data(this is where the process's PCB comes in). A process can also be suspended and placed in a blocked state if it needs a resource such as I/O i.e. a read system call on UNIX.

Now this is where the question begins... Threads share many characteristics with processes, so much so that a thread is sometimes called a lightweight process. There exist some MAJOR differences though. A thread unlike multiple processes, shares the same memory space with other threads of that process. Each thread has its own implemented registers, stack and other information for the threading scheduler to schedule the threads. System calls on UNIX such as read are blocking calls and will suspend the process. The author mentioned that we could edit the kernel to allow for system calls such as read() to be non-blocking (i.e. they will return 0 bytes if no input was received) but this may break compatibility with older programs and is not an ideal situation, not to mention that the threading library, it's function and the threads will reside in kernel memory.

So a problem with user-level threading libraries could be that one thread could block the whole process, and again this would defeat the purpose of threading. The author proceeds to say that threading libraries can allow the thread to call read(), if read() won't block, it can do this in advance by using the select() system call. If a thread uses the select call and finds out that a read will cause the process to be blocked the threading scheduler can then switch to another thread that will not block the process.

Here is my main question, if any more spring to mind I'll update.

Q: When the author says we can check if a read call will block, such as getting input from the keyboard then we can use the select system call to tell us whether a read call will block. But surely if a user-level programs thread wants to do some I/O with a read call, wouldn't a read call always block?? So if that's the case wouldn't that mean that the user programs I/O thread will never run? If not, what is a case where the read call for getting input from the user will not be a blocking call?



*Note: The reason I'm placing this in general C++ as C or C++ code examples are more than welcome and actually will help, Thanks.

*Note: This post refers to UNIX systems
Last edited on
The implementation of "threads" can be different on each operating system. In the Linux kernel/scheduler, there are only "tasks". So, what we usually see as "processes" or "threads" in the user space, all really are just "tasks" to the kernel/scheduler. If you start a new "process", then that is just a new "task" to the kernel/scheduler. And if a "process" then starts a new "thread", which is done using the clone() syscall with the CLONE_THREAD flag, then that's again just a new "task" to the kernel/scheduler! The only real difference between a "process" and a "thread" is that, when a new "thread" is created, then the corresponding "task" is placed in the same "thread group" as the calling "process" (task), and that it shares the same memory space with its sibling "threads", i.e. the "tasks" belonging to the same "thread group".

Whether a read() syscall blocks or not, that does not really have anything to do with multi-threading. Instead, a file descriptor (FD) can be opened in "blocking" or "non-blocking" mode. If the FD was opened in "blocking" mode, then the read() syscall will block until some data becomes available for reading, if no data is available for reading at the moment. While the read() syscall is blocked, the corresponding task (process or thread) is removed from the scheduler's "ready" queue and is put to a separate "I/O waiting" queue, so the no CPU time gets wasted. As soon as some data becomes available for reading, the task is again inserted into the scheduler's "ready" queue and, eventually, will be able to proceed. Conversely, if the FD was opened in "non-blocking" mode, then the read() syscall will either immediately return some data, or, if no data is available for reading at the moment, it will immediately fail, with an EAGAIN or EWOULDBLOCK error code.

If you don't want your application to block on I/O operations, you have at least three options: (1) Create a separate "worker" thread to perform your I/O operations, so that, if a (blocking) I/O operation blocks, then only the "worker" thread (but not the whole application) gets blocked. (2) Use "non-blocking" mode, so that I/O operations will fail immediately, instead of blocking. (3) Use the poll() or epoll() syscall to monitor your file descriptors and only start the I/O operation when the file descriptor is "ready" for the desired type of I/O operation. Note that, if poll() signals that a file descriptor is "ready", then this guarantees that the subsequent (blocking) I/O operation will not block.
Last edited on
I’m not sure if the book I’m reading is outdated or if it’s based on older standards of POSIX, the book was written just when windows 8 was released.

Really great insight, I’ll dissect it in more detail tomorrow morning. I never knew about clone(). Would this mean that when we create threads with clone() then they are kernel level threads and not user level? Also would a library like Pthreads be kernel or user level threads? If hypothetically Pthreads used clone() to create a thread would this make it a kernel level threading library?
Last edited on
Again: What we see as "processes" or "threads" in the user-space, are really just "tasks" (or whatever terminology your specific OS uses) to the kernel/scheduler. What we usually call "threads" are nothing but a bunch of tasks that are sharing the same virtual memory space and the same PID (aka "thread group identifier"). And each group of such tasks is what we refer to as a "process".

Each OS has its own "low-level" API to create and manage "threads". For example, Linux uses the clone() syscall with specific flags to create a new thread, whereas FreeBSD has a thr_new() syscall and Windows would use the CreateThread() function. User-space libraries, such as PThreads, are just an additional abstraction layer on top of the OS-specific "low-level" API, which makes managing threads much easier and more portable. Originally, PThreads was created to have a "standardized" threading API across various variants of the Unix operating systems, but nowadays ports are available for Linux, Windows and some other OS. Internally, those user-space threading libraries still use the "low-level" API of the underlying OS! Programming languages with built-in threading support, such as <std::thread> in C++, <threads.h> in C11, or std::thread in Rust, either build on top of the system's "low-level" API or they wrap the PThreads library.

There also is a thing called "fiber", which is a kind of "lightweight thread" that is implemented completely in user-space and that can work without any support from the OS kernel, usually relying on cooperative multitasking. But that's a whole different topic. Normal user-space threading libraries, such as PThreads, do not create "fibers", but actual OS-level threads! There are dedicated libraries for "fiber" support, such as boost::fibers or GNU Portable Threads. Be aware that "fibers" do not benefit from multiple processors (CPU cores), because the kernel/scheduler can not distribute "fibers" across multiple processors, because the OS usually is not even aware of their existence.

https://en.wikipedia.org/wiki/Fiber_(computer_science)
https://www.boost.org/doc/libs/1_78_0/libs/fiber/doc/html/fiber/overview.html
Last edited on
the distinction on threads and processes is almost always overexplained and convoluted in textbooks. I didn't really understand it for a while either, because of the poor wording. You almost need to complete your degree before you can unravel what they are saying in the worst books!

That aside, beware older books as c++ now has its own threading library and without a good reason to do so, you should be using that. It will do the right thing on your OS.

As far as blocking and nonblocking go, its is exceedingly aggravating on all the OS to set up a kbhit type function. If you want to understand some of this hooplah, try doing that 'simple' task without using a premade one from a nonstandard library. Its doable, but surprisingly aggravating and a great exercise. This is the cornerstone of a lot of games where for example holding down a key is used to move in real time... not much fun if you have to wait for input!!!

if you can do this, using any kind of threading (prefer the C++ "new" stuff) ... you will probably have your answers.
Last edited on
the book I’m reading


Which one?
Starting to make more sense now. The book I’m reading must be outdated as it seems that Pthreads and other threading libraries were mainly implemented as user-level threads when said book was written

“using pthreads create threads in the userspace, and the kernel is not aware about this and view it as a single process only, unaware of how many threads are inside.


This was how userspace threads were done prior to the NPTL (native posix threads library). This is also what SunOS/Solaris called an LWP lightweight process.”

The above was taken from https://stackoverflow.com/questions/39185134/how-are-user-level-threads-scheduled-created-and-how-are-kernel-level-threads-c

So maybe the book was written prior to NPTL? The book is “modern operating systems 4th edition” by Tananbuam and Bos.

The one thing that still has me confused is, if threads and processes are now both considered tasks by most Linux kernels. Surely the OS would treat processes the same? I.e. both entities would share the CPU. Let’s say if a thread did block then how would the OS determine to run another thread from the current process instead of another process altogether (if threads and processes are both tasks and considered equal)?
The scheduler of the OS generally deals with "tasks" (aka "schedulable entities"). Whenever the current task has used up its time slice (or it blocks on an I/O operation), then the scheduler picks the next task to run, from the "ready" queue. How the scheduler decides which task should run next (if there are multiple tasks waiting in the "ready" queue), that totally depends on the selected scheduling policy!

You probably want to read something about Linux' "completely fair scheduler" (CFS) for more details:
https://developer.ibm.com/tutorials/l-completely-fair-scheduler/

Whether a "process" is single-threaded, i.e. there is just one task alone it its thread group, or whether it is multi-threaded, i.e. there are several tasks in the same thread group (which also share the same memory space), doesn't really matter here. The scheduler still deals with individual tasks! Of course, in theory, there could be a scheduling policy that prefers switching to a task that belongs to the same thread group (process) as the one that was running previously. But that certainly is not a requirement. I don't think the CFS works like this.

BTW: I think your book is a bit outdated, as Linux has "proper" thread support via NPTL since version 2.6, release in 2003 😊


create threads in the userspace, and the kernel is not aware about this and view it as a single process only, unaware of how many threads are inside.
That is actually the definition of a "fiber" (aka "lightweight thread").

I think even on "early" Linux (before NPTL), "threads" were not actually implemented like this, but already used the clone() syscall:

Before the 2.6 version of the Linux kernel, processes were the schedulable entities, and there were no special facilities for threads [in the kernel]. However, it did have a system call — clone — which creates a copy of the calling process where the copy shares the address space of the caller. The LinuxThreads project used this system call to provide kernel-level threads. Unfortunately, it only partially complied with POSIX, particularly in the areas of signal handling, scheduling, and inter-process synchronization primitives.
Last edited on
modern operating systems 4th edition” by Tananbuam (sic)


There is now a 5th edition from 2023:
https://www.amazon.co.uk/Modern-Operating-Systems-Global-Tanenbaum-dp-1292459662/dp/1292459662/ref=dp_ob_title_bk

However IMO Tanenbaum books whilst well known are not really for self study but to accompany a taught OS course.
The book so far isn't too bad, but I definitely see why it's a companion book. Is there any books that would be more geared towards self-study (on operating systems)?
My OS books relate to the 1970's so not really applicable for now. The only 'newer' os books I have are an older version of 'Modern Operating Systems' and his 'Operating Systems Design and Implementation' book regarding Minix.

The other well known OS book is 'Operating Systems: Internals and Design Principles' by Stallings. But again, this is for a university course:
https://www.amazon.co.uk/Operating-Systems-Internals-Design-Principles/dp/1292214295/ref=sr_1_6
You might want to have a look at this book:
https://www.amazon.co.uk/Operating-System-Concepts-Abraham-Silberschatz/dp/1118063333/ref=sr_1_101

I don't have personal knowledge of this one but a colleague has suggested it.
The operating system implements a scheduler which gives each process a time quantum to use the CPU.


This is only true if the OS doesn't support threads. If the OS supports threads then the scheduler gives each thread a time quantum to use the CPU and priority is by thread. Execution is by thread and not by process. Each process will have at least one executable thread. If a process has no associated thread then the process is destroyed. Every thread has a relationship with its associated PCB.

For I/O, you usually have producer and consumer. For say keyboard the producer is the keyboard keys and the consumer is the os call to obtain keyboard input. Simply, the keyboard keys will raise an interrupt when one is pressed. The associated key code is placed in the 'front' of the buffer (often cyclic). When the consumer requires a key then it obtains one from the 'back' of the buffer. If the buffer is empty then the consumer waits until one is available (blocking) or indicates in some way that one isn't available and returns (non-blocking). A good design will provide some functionality for the consumer to know if the buffer contains chars before an attempted read if blocking i/o is used (it is in c/c++ and that's why kbhit() was introduced into conio.h for Windows). Different os will handle this differently.

If the os is only input blocking then if you want non-blocking input then if the os supports threads then you create a new thread for input and use events to signal when input is available to the main thread. Note that c++ input is blocking for console input and non-blocking for file input.

Also note that blocking and non-blocking often have a different meaning when used with say reading from a file as opposed to reading from the console. For a file, blocking input is when the read function waits for the requested data to be read from the file or an error returned. File reading will usually incur a delay whilst the contents are acquired (blocking). Non-blocking file input is when a read request is scheduled and the read function returns immediately. At some future time when the read has been performed the thread is advised that the data is now available (eg via an event).
Last edited on
As pointed out before, "processes" and "threads" are user-space concepts, whereas the kernel/scheduler deals with "tasks". Before "multi-threading" became a thing, there was a 1:1 relationship between user-space "processes" and kernel "tasks" – except that the kernel additionally maintains internal "tasks" that aren't visible from user-space at all – which is why these terms were often used synonymously.

Nowadays, a "task" is pretty much the kernel-equivalent of a user-space "thread". Multiple "tasks" can be grouped together, forming a so-called "thread group", allowing those "tasks" to share the same memory space as well as the same TGID (thread group ID). So, what you see in user-space, as a "process" with multiple "threads", is in fact just a bunch of related "tasks" in the kernel. Also, the PID (process ID) that you see in user-space, e.g. by calling getpid(), is in fact the TGID. Of course, you can also still have a "process" with only a single "thread", which is really just a "task" that is alone it its thread group. Anyways, the scheduler is always scheduling individual tasks (i.e. "threads").

To make things even more confusing, there was a period of time when the kernel did not have the proper infrastructure for supporting "threads" yet, but people still emulated "threads" with the help of user-space libraries – using separate "processes" that communicate via signals as a workaround. This is not to be confused with "fibers" (lightweight threads), which is an entirely different concept!

Nowadays, we still normally use user-space libraries, such as PThreads or std::thread, for managing the "threads" (instead of directly messing with the OS' "low-level" API), but that is mainly for having a simpler API. And for portability between different OS!
Last edited on
*Note: This post refers to UNIX systems


Are you after info relating to Unix os (Linux?) or to os in general? If specific then consider 'Understanding Linux Kernel' by Daniel Plerre:
https://www.amazon.co.uk/Understanding-Linux-Kernel-Daniel-Plerre/dp/0596005652/ref=sr_1_6

Each os is different in their implementation (and possibly terminology) so Windows os implementation is different from Linux etc.
Last edited on
As pointed out before, "processes" and "threads" are user-space concepts


With Windows, processes and threads are dealt with by the kernel (kernel32.dll).
If specific then consider 'Understanding Linux Kernel' by Daniel Plerre

Not that it really matters your link lists different authors for the 3E Linux kernel book.

On Amazon US there are newer Linux kernel books available by yet another author, published by Packt:

https://www.amazon.com/Linux-Kernel-Development-Cookbook-programming/dp/178995343X/

https://www.amazon.com/Linux-Kernel-Programming-practical-synchronization-dp-1803232226/dp/1803232226/
With Windows, processes and threads are dealt with by the kernel (kernel32.dll).

Nitpicking: not quite 😉

kernel32.dll, gdi32.dll, user32.dll and so on are the user-space libraries that provide the Win32 API. The names are "historic".

ntdll.dll is a user-space library that provides the Native API, i.e. the "syscall" interface to the NT kernel. Normally this is library is not linked directly to the applications, but instead it gets called indirectly via the Win32 API libraries that the applications are supposed to use.

ntoskrnl.exe is the actual Windows NT kernel image.


It looks like this:
https://i.imgur.com/o2zVlqu.png 💡

See also:
https://en.wikipedia.org/wiki/Microsoft_Windows_library_files#Win32_API
Last edited on
Not that it really matters your link lists different authors for the 3E Linux kernel book.


Daniel Plerre Bovet

I just copied the name from the url....

On Amazon US there are newer Linux kernel books available


Also available in the UK but I understood more about kernel programming than os description....
Last edited on
Topic archived. No new replies allowed.