Understanding Concurrency and Parallelism: A Guide with Examples

Understanding Concurrency and Parallelism: A Guide with Examples

In the world of computing, concurrency and parallelism are fundamental concepts for optimizing performance, but they are often misunderstood. This article clarifies their differences, use cases, and practical implementations, with code examples in Python.


1. Definitions

Concurrency

Concurrency is about managing multiple tasks in overlapping time periods. It does not necessarily mean tasks are executed simultaneously. Instead, the system interleaves tasks to make progress on all of them.

  • Example: A single chef switching between chopping vegetables and stirring a soup.

Parallelism

Parallelism involves executing multiple tasks simultaneously using multiple processing units (e.g., CPU cores).

  • Example: Two chefs working independently—one chopping vegetables while the other stirs soup.


2. Key Differences

Article content

3. How They Work

Concurrency in Action

Even on a single-core CPU, concurrency is achieved by rapidly switching between tasks (time-slicing).

  • Analogy: A student alternating between homework and replying to messages.

Example: Concurrent Web Requests (Python Threads)

import threading
import requests

def download(url):
    response = requests.get(url)
    print(f"Downloaded {url} (status: {response.status_code})")

urls = ["https://example.com", "https://example.org", "https://example.net"]

threads = []
for url in urls:
    thread = threading.Thread(target=download, args=(url,))
    thread.start()
    threads.append(thread)

for thread in threads:
    thread.join()
        

Parallelism in Action

Tasks run simultaneously on separate cores.

Example: Parallel Image Processing (Python Multiprocessing)

import multiprocessing
from PIL import Image

def process_image(image_path):
    img = Image.open(image_path)
    img.rotate(90).save(f"rotated_{image_path}")
    print(f"Processed {image_path}")

image_paths = ["img1.jpg", "img2.jpg", "img3.jpg"]

with multiprocessing.Pool() as pool:
    pool.map(process_image, image_paths)
        

4. When to Use Each

Concurrency

  • I/O-bound tasks: Tasks waiting for external resources (e.g., APIs, databases, files).
  • Examples:Web servers handling multiple clients.GUIs staying responsive while performing background tasks.

Parallelism

  • CPU-bound tasks: Tasks requiring heavy computation.
  • Examples:Training machine learning models.Rendering video or 3D graphics.


5. Challenges

Concurrency Challenges

  • Race conditions: Shared data modified by multiple threads.
  • Deadlocks: Threads waiting indefinitely for resources.
  • Solutions: Locks (mutexes), async/await (e.g., Python’s asyncio).

Parallelism Challenges

  • Overhead: Spawning processes and inter-process communication (IPC).
  • Scalability: Limited by Amdahl’s Law (theoretical speedup based on parallelizable code).


6. Hybrid Approaches

Modern systems often combine both:

  • Web servers: Use multiple processes (parallelism) with threads in each process (concurrency).
  • Data pipelines: Process data in parallel across cores while managing I/O concurrently.


7. Common Misconceptions

  • Myth: "Concurrency is parallelism." Truth: Concurrency is about structure; parallelism is about execution.
  • Myth: "More threads/processes always improve performance." Truth: Overhead (e.g., context switching) can degrade performance.


8. Conclusion

  • Concurrency optimizes resource usage for tasks that wait (I/O-bound).
  • Parallelism accelerates tasks that compute (CPU-bound).

By understanding these concepts, developers can design systems that are both efficient (concurrency) and fast (parallelism). For instance:

  • A video streaming service uses concurrency to handle user requests and parallelism to encode videos.
  • Always profile your code to determine bottlenecks before choosing an approach!

To view or add a comment, sign in

More articles by David Zhu

Others also viewed

Explore content categories