Operating System Role
An operating system (OS) sits between hardware and application software, consisting of a kernel and system interfaces. The OS exclusively controls hardware resources, while apps interact via OS-provided APIs. Key functions include abstracting low-level hardware operations and managing orderly resource competition.
Multiprogramming Technique
To break the single-task execution bottleneck, the OS uses switching with state preservation:
- Switch Triggers: IO blocking, execution timeout, or higher-priority task arrival
- State Preservation: Saves a process’s context before switching to resume later
- Implementation: Space multiplexing (loading multiple apps into memory) and time multiplexing (CPU time slicing)
- Key Notes: Processes are physically isolated for safety; IO-bound tasks see efficiency gains, while CPU-bound tasks may see overhead from switcihng.
Process vs Program
A program is static source code; a process is a running program instance managed by the OS.
Process States & Execution Modes
- Serial: Single-task sequential execution
- Concurrent: Multiprocessing with single CPU + time multiplexing
- Parallel: True simultaneous execution on multi-core CPUs
- Blocked: Process waiting on IO operations
Process Identification
Use os.getpid() and os.getppid() to get a process’s ID and parent process ID.
Multiprocessing Implementation in Python
Two Methods to Create Processes
Method 1: Instantiate Process Class
from multiprocessing import Process
def worker(worker_id):
print(f"Worker {worker_id} is running")
if __name__ == '__main__':
p = Process(target=worker, args=('p01',))
p.start()
print("Main process completes")
Method 2: Inherit from Process Class
from multiprocessing import Process
class CustomWorker(Process):
def __init__(self, worker_name):
super().__init__()
self.worker_name = worker_name
def run(self):
print(f"CustomWorker {self.worker_name} is executing")
if __name__ == '__main__':
w = CustomWorker('w02')
w.start()
Critical Notes: Windows requires process creation under __main__ to avoid recursive forking.
Memory Isolation Between Processes
from multiprocessing import Process
import time
initial_value = 1000
def modify_value():
time.sleep(2)
global initial_value
initial_value = 0
print(f"Child process value: {initial_value}")
if __name__ == '__main__':
print(f"Main process initial value: {initial_value}")
p = Process(target=modify_value)
p.start()
p.join()
print(f"Main process final value: {initial_value}") # Output remains 1000
Process Methods and Attributes
start(): Sends a request to the OS to start a child processjoin(): Blocks the parent until the child completesterminate(): Sends a termination signal to the childis_alive(): Returns the child’s active statusname/pid/exitcode: Process metadata
Daemon Processes
A daemon process monitors another process and terminates when the monitored process completes. It cannot spawn sub-processes.
from multiprocessing import Process
import time
def monitor():
print("Daemon process running...")
time.sleep(4)
print("Daemon process exiting...")
if __name__ == '__main__':
p = Process(target=monitor)
p.daemon = True
print("Main process starting...")
p.start()
time.sleep(2)
print("Main process completes")
Process Safety Issues
Concurrency leads to race conditions when accessing shared resources. Use a mutex lock to serialize critical sections.
from multiprocessing import Process, Lock
import json
import time
def check_tickets(buyer, db_file):
with open(db_file, 'r') as f:
data = json.load(f)
print(f"{buyer} checks tickets: {data['remaining']}")
def buy_ticket(buyer, db_file, lock):
lock.acquire()
time.sleep(0.5)
with open(db_file, 'r') as f:
data = json.load(f)
if data['remaining'] > 0:
data['remaining'] -= 1
with open(db_file, 'w') as f:
json.dump(data, f)
print(f"{buyer} successfully bought a ticket")
else:
print(f"{buyer}: Tickets sold out")
lock.release()
def ticket_task(buyer, db_file, lock):
check_tickets(buyer, db_file)
buy_ticket(buyer, db_file, lock)
if __name__ == '__main__':
with open('ticket_db.json', 'w') as f:
json.dump({'remaining': 2}, f)
lock = Lock()
buyers = ['Alice', 'Bob', 'Charlie']
processes = [Process(target=ticket_task, args=(b, 'ticket_db.json', lock)) for b in buyers]
for p in processes:
p.start()
for p in processes:
p.join()
Inter-Process Communication (IPC)
Processes are memory-isolated; use these methods for communication:
- Shared files (disk-based, low speed)
- Shared memory (fast, limited size)
- Pipes (unidirectional, OS-encapsulated)
- Sockets (local/remote, network-based)
Example with Manager for shared memory:
from multiprocessing import Process, Manager, Lock
def update_counter(shared_dict):
shared_dict['count'] += 1
print(f"Child process counter: {shared_dict['count']}")
if __name__ == '__main__':
with Manager() as manager:
shared_data = manager.dict({'count': 10})
print(f"Before update: {shared_data['count']}")
lock = Lock()
p = Process(target=update_counter, args=(shared_data,))
p.start()
p.join()
print(f"After update: {shared_data['count']}")
Queue
A queue implements FIFO behavior. Use Queue from multiprocessing:
from multiprocessing import Process, Queue
import time
import random
def baker(q):
for i in range(1, 6):
time.sleep(random.randint(1, 2))
bun = f"Steamed Bun #{i}"
q.put(bun)
print(f"Baker made {bun}")
def eater(q):
for i in range(5):
bun = q.get()
time.sleep(random.randint(1, 2))
print(f"Eater ate {bun}")
if __name__ == '__main__':
q = Queue()
p1 = Process(target=baker, args=(q,))
p2 = Process(target=eater, args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()
JoinableQueue
Extends Queue with task_done() and join() to track completion of queue items:
from multiprocessing import Process, JoinableQueue
import time
import random
def baker(q):
for i in range(1, 6):
time.sleep(random.randint(1, 2))
bun = f"Steamed Bun #{i}"
q.put(bun)
print(f"Baker made {bun}")
def eater(q):
while True:
bun = q.get()
time.sleep(random.randint(1, 2))
print(f"Eater ate {bun}")
q.task_done()
if __name__ == '__main__':
q = JoinableQueue()
p_baker = Process(target=baker, args=(q,))
p_eater = Process(target=eater, args=(q,))
p_eater.daemon = True
p_baker.start()
p_eater.start()
p_baker.join()
q.join()
print("All buns consumed")