Execution & Concurrency Models
These two terms are often used interchangeably, but they represent distinct concepts. Understanding the difference is crucial for designing efficient and scalable applications.
Concurrency: Concurrency is about dealing with multiple tasks at once. It's a concept related to the structure of a program. A concurrent program is one where different parts can be in progress at the same time. This doesn't necessarily mean they are running at the same time. For example, on a single-core CPU, the system can switch between different tasks, giving the illusion of simultaneous execution.
Parallelism: Parallelism is about doing multiple tasks at once. It's a concept related to the execution of a program. A parallel program is one where multiple tasks are running simultaneously, typically on different CPU cores. Parallelism is impossible on a single-core machine.
Key takeaway: Concurrency is about structure, parallelism is about execution. You can have a concurrent program that doesn't run in parallel (e.g., on a single-core CPU), but you cannot have parallelism without a concurrent design.
Threads are the most common model for achieving parallelism on multi-core systems.
The Challenge: Shared State
Because threads share memory, they can read and write to the same variables. This is powerful but also dangerous. If two threads try to modify the same variable at the same time, you can get a race condition, leading to corrupted data and unpredictable behavior.
To prevent this, you need synchronization mechanisms:
// Java example of a synchronized method (using a lock)
public class Counter {
private int count = 0;
// The 'synchronized' keyword ensures that only one thread can execute this method on a given instance at a time.
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
# Python example using a Lock
import threading
class Counter:
def __init__(self):
self.count = 0
self.lock = threading.Lock()
def increment(self):
# Acquire the lock before modifying the shared resource
with self.lock:
self.count += 1
def get_count(self):
return self.count
This model is an alternative to traditional threading, popularized by Node.js and now common in many languages like Python, C#, and Rust. It's a form of cooperative multitasking.
Instead of relying on the OS to switch between threads, the program itself yields control voluntarily. It's particularly well-suited for I/O-bound tasks.
How it works:
The async and await keywords are syntactic sugar that makes this asynchronous code look and feel like synchronous code.
// JavaScript (Node.js)
async function fetchData() {
console.log("Starting to fetch data...");
// The 'await' keyword pauses this function, but not the whole program.
// The event loop can run other tasks while waiting for the network.
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log("Data fetched!");
return data;
}
console.log("Before calling fetch.");
fetchData();
console.log("After calling fetch. This line runs immediately.");
# Python
import asyncio
async def fetch_data():
print("Starting to fetch data...")
# 'await' pauses this coroutine and allows the event loop to run other tasks.
await asyncio.sleep(2) # Simulate a network request
print("Data fetched!")
return {"data": "some_data"}
async def main():
print("Before calling fetch.")
await fetch_data()
print("After calling fetch.")
asyncio.run(main())