Q1. What is the difference between threading and multiprocessing?
Threading runs multiple threads within the same process, sharing memory, but due to GIL, true parallelism for CPU-bound tasks is limited. Multiprocessing spawns separate processes, each with its own memory and Python interpreter, allowing true parallel execution. Use threading for I/O-bound tasks, multiprocessing for CPU-bound tasks.
Q2. What is the Global Interpreter Lock (GIL)?
The GIL is a mutex that prevents multiple threads from executing Python bytecode simultaneously. It ensures thread safety but limits parallelism for CPU-bound tasks. I/O-bound tasks can still benefit from threading because they release the GIL during I/O operations.
Q3. How do you create and start a thread?
Use the threading module. Define a function, then create Thread object with target, and call start(). Example:
import threading
def worker():
print("Working")
t = threading.Thread(target=worker)
t.start()
t.join()Q4. What is a race condition and how do you prevent it?
A race condition occurs when multiple threads access shared data concurrently, leading to inconsistent results. Prevent with locks (threading.Lock), semaphores, or use thread-safe data structures. Example:
lock = threading.Lock()
lock.acquire()
# critical section
lock.release() Or use with lock:.Q5. How do you use a process pool?
Use concurrent.futures.ProcessPoolExecutor or multiprocessing.Pool. Example:
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor() as executor:
results = executor.map(square, numbers)