MushcatShiro's Blog

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.

#Operating System