Check the health and availability of your Linux servers for optimal performance with Site24x7's Linux monitoring tool.
In the programming world, the words “process” and “thread” are often used interchangeably. In reality, they’re two entirely separate constructs.
A process is a program in execution. For example, all the applications we use on our computers and mobile devices run as a process in the operating system. Processes have associated memory and other resources allocated to them by the operating system. Multiple processes can be run simultaneously on modern operating systems, but they are intentionally isolated from each other to avoid one process affecting another.
Sometimes called a lightweight process, a thread is really a unit of execution inside a process. While a process can have anywhere from 1 to 100s of threads to do its work concurrently, threads within a single process all share the process’ resources. This means that an error/bug in one thread will almost always affect other threads within the same process.
This article will explore how processes are different from threads, how to create them, what their resource model is, and which one is most useful.
In Linux, there are two system calls for creating a process, fork and execve.
First, the fork
uses an internal clone
system call to create a clone of the process that is requesting a new process be created. Then, the execve
system call replaces the executable of the newly cloned process.
To see it in action, use the guidance below to create a process and trace the system calls. For additional help, use the strace
command to dig into the system calls during a command execution.
strace -f -etrace=execve,clone bash -c '{ date; }'
Only the output of this command for clone
and execve
system calls should be visible in the output, as shown below.
execve("/usr/bin/bash", ["bash", "-c", "{ date; }"], 0x7ffe2658f290 /* 63 vars */) = 0
clone(child_stack=NULL,
flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0x7f7c2f3c6a10) = 1132
strace: Process 1132 attached
[pid 1132] execve("/usr/bin/date", ["date"], 0x561c3127c3b0 /* 62 vars */) = 0
Sat 22 Jan 2022 06:35:05 AM UTC
[pid 1132] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1132, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
First, a clone process is created with PID 1132. Then, execve
is used to replace the executable of the process to /usr/bin/date
.
The same clone
system call can be used to spawn threads with the following method signature:
pid = clone(function, stack ptr, sharing flags, arg)
Passing different flags to the clone function makes it possible to create either a process or a thread. Some of the most-used flags are noted in the table below, and a complete list can be found on the Linux manual pages.
FLAG | When flag is set |
When flag is not set |
---|---|---|
CLONE_VM | Creates a thread by sharing the memory of the current process with the cloned process. | Creates a process by allocating separate memory to the cloned process. |
CLONE_PARENT | The parent of the clone process will be the same as the parent of the calling process. | The parent of the clone process will be the calling process. |
Once a process starts, it can spawn its threads using the clone
system call. However, it’s best to use the pthread_create
system call which abstracts the actual implementation details and avoids portability issues. It’s also worth noting that pthread_create
internally calls the clone system call to create threads.
Both processes and threads are represented by a data structure called task_struct. This data structure holds the information about a process, its threads, scheduling parameters, machine registers, system calls state, and file descriptors. The operating system holds a list of these data structures called a task list, which forms a list of all the running processes and threads.
Now, to monitor the running processes and threads in the Linux system, use the ps command to get the list:
ps -eLF
This command will generate a list of all the running processes and their associated threads.
Fig. 1. List of running processes and threadsIn this output, note the PID, PPID, LWP,
and NLWP
.
NLWP
gives you the number of threads each process has, and LWP
gives you the ID
for the thread. If NLWP
is 1, it's a single-threaded process and you’ll note that the PID
is equal to LWP
(thread ID). In a single-threaded process, thread and process are functionally the same thing.
For multi-threaded processes, one of the threads will always have the same ID as the PID
.
The next section will cover how resource sharing works differently in processes and threads.
As stated above, processes in Linux are isolated from each other, but threads share the same memory space and other resources.
Sharing memory makes inter-thread communication easy, as threads can share variables among each other. However, inter-process communication is slightly more complicated and requires advanced constructs such as pipes or sockets. Due to these differences, inter-thread communication is much faster than inter-process communication.
There is no one-size-fits-all formula to decide between multi-process and multi-thread implementation. It is a matter of choosing the right tradeoffs. If you have to implement a program with concurrent processing needs, you can choose either processes or threads to implement it.
To keep resources in a program isolated from each concurrently running code, implement that layer with processes. Within each isolated process, implementing multi-threading will provide fast inter-thread communication due to lightweight context switching and easy data sharing.
On the contrary, if an entire process is implemented as multiple threads, errors in one thread could tank the entire process. Also, running multiple processes instead of threads leads to an overall low-memory footprint, as the state of low-priority or inactive processes can be saved out to disk to make room for other running processes. Conversely, no such construct can be applied to multi-threaded processes.
Google Chrome provides a useful example. Chrome uses one process per tab, but within a tab it uses multiple threads to ensure all the activities related to that tab (rendering, network calls, etc.) run concurrently. By using such an approach, Chrome can isolate problems in one tab so they don’t affect other tabs. This also keeps the overall memory utilization low, as the operating system swaps out the inactive tabs to make room for active tabs.
In short, a good mix of multi-process and multi-thread methods is the way to achieve the best concurrency and good resource utilization.
Write for Site24x7 is a special writing program that supports writers who create content for Site24x7 “Learn” portal. Get paid for your writing.
Apply Now