This is a page about »Multitasking«.
unless explicitly called out, the discussion is limited to Unix systems
Definition
Concurrency: two or more task running at an overlapping time period, but does
not imply running at the same tick. One can think of concurrency is about
scheduling, i.e. sending task to idle resource.
Parallelism: two or more task running at the same time. Parallelism is about resource capacity. There is no parallelism if there is only a single resource
resource can be processing unit or equivalent
Process Management With C
prefork creates process in advance and idle until task is assigned, since
it is preloaded it has a lower latency at the cost of higher memory usage. it
uses fork
, wait
and IPC mechanisms (pipe
). some examples including
daemons, web servers and etc.
spawn creates process during runtime for designated task hence there is
some latency while being more efficient per process. fork-exec
or
posix_spawn
. some examples including make
and system calls.
When fork
happens a child process is created with exact copy of all memory
segments of parent process, two same process/program, the virtual memory, copy
On write semantics are implemented hence physical memory need not to be copied.
fork
creates a new PID, and returns the child’s PID to parent process while
returning 0 to child process (?). By returning child PID, it allows user to
decide to block the parent until child process terminated through
wait
/waitpid
(potentially becoming zombie process) or to move on. When a
child process would like to switch to run another executable, exec
is used to
replace current process image with new program, hence
fork-exec.
1#include <stdio.h>
2#include <stdlib.h>
3#include <unistd.h>
4#include <sys/wait.h>
5
6#define NUM_WORKERS 4
7
8void handle_connection(int worker_id) {
9 printf("Worker %d handling request in PID %d\n", worker_id, getpid());
10 sleep(5); // Simulate work
11}
12
13int main() {
14 for (int i = 0; i < NUM_WORKERS; i++) {
15 pid_t pid = fork(); // here will branch two process, parent and child
16 if (pid == 0) { // if child
17 // Child: loop to handle work
18 while (1) {
19 handle_connection(i); // Simulated work
20 }
21 exit(0);
22 }
23 }
24
25 // Parent process waits (or handles other logic)
26 for (int i = 0; i < NUM_WORKERS; i++) {
27 wait(NULL);
28 }
29
30 return 0;
31}
Compared to threading, each task/process are isolated and can only communicate through IPC. Threading on the other hand has same address space hence communication is easier at the risk of race condition.
In Unix system, kernel startup has PID 0 and it creates a special process
init
/systemd
depending on system, with PID 1 using internal function instead of fork. Subsequent user space process are forked or spawned as child of PID 1
Fork Bomb
OS/system level protection by setting ulimit
or /proc/sys
. Otherwise, code
must be handled properly.
1:(){ :|:& };:
2
3:(){ # function called : and takes no argument
4 'calls itself using recursion and pipes output to another : function
5 the function is called two times
6 & puts the function call in the background such that child process can\'t be killed
7 '
8 :|:&
9};: # ; terminate function definition, : calls the function
Coroutine
Coroutine is a control structure at user level usually implemented by programming language or library instead of OS level process management. It runs entirely in user space with no system call to kernel. OS scheduler is only concerned about threads and processes.
Coroutine is a cooperative “scheduled” (designed) while OS managed tasks are preemptive scheduled. Coroutine isolation (memory and stack) is up to the implementation.