Scrapeless Wiki

Concurrency vs. Parallelism

Comparison P2 concurrency vs parallelism
Overlaps a live page. A published page already targets this keyword: https://www.scrapeless.com/en/glossary/concurrency-vs-parallelism. Publishing both risks cannibalisation β€” decide before this goes live.

Learn the difference between concurrency and parallelism, why I/O-bound work needs one and CPU-bound work the other, and how to pick for a scraping pipeline.

Concurrency is dealing with many things at once. Parallelism is doing many things at once. The distinction sounds pedantic until you pick the wrong one and get no speedup at all.

1. Concurrency

Concurrency is a structure: multiple tasks are in progress, interleaved, so that when one waits another runs.

A single thread can be highly concurrent. While one request waits for a response β€” doing nothing, just waiting β€” the thread switches to another. One worker, many tasks in flight.

  • Requires: a way to suspend a task while it waits.
  • Wins when: tasks spend most of their time waiting.
  • Achieved by: async/await, event loops, green threads, coroutines.

2. Parallelism

Parallelism is execution: multiple tasks run simultaneously on different CPU cores.

Genuinely at the same instant, on separate hardware. Four cores, four tasks, four times the computation.

  • Requires: multiple cores.
  • Wins when: tasks are doing actual computation.
  • Achieved by: multiple processes, or threads in runtimes without a global lock.

3. The Distinction That Matters

Concurrency Parallelism
Definition Tasks make progress in overlapping periods Tasks execute at the same instant
Needs multiple cores No Yes
Helps with Waiting Computing
Bottleneck it addresses I/O CPU
Typical mechanism Event loop, async Processes
Memory cost Low β€” one process High β€” one per process
Sharing data Easy, same memory Requires IPC or serialisation

4. Which Your Workload Needs

Ask what the work is actually spending time on.

I/O-bound β€” waiting on the network, disk, or a database. A scraper fetching a page spends 95% of its time waiting for bytes to arrive. Adding cores does nothing; the CPU was idle already. Concurrency is the answer.

CPU-bound β€” parsing, compressing, hashing, image processing, model inference. The processor is saturated. Interleaving does not help because nothing is waiting. Parallelism is the answer.

A scraping pipeline is usually both, in sequence: fetching is I/O-bound, parsing is CPU-bound. That is why the effective design is often concurrent fetching feeding a parallel parsing stage.

5. In Python Specifically

Python makes this concrete because its global interpreter lock forces the choice.

# I/O-bound: concurrency. One process, many requests in flight.
async with httpx.AsyncClient() as client:
    results = await asyncio.gather(*[client.get(u) for u in urls])
# CPU-bound: parallelism. Separate processes sidestep the GIL.
with ProcessPoolExecutor() as pool:
    parsed = list(pool.map(parse_html, documents))

Threads in Python are useful for I/O β€” the lock is released while waiting β€” but give no speedup on pure computation, because only one thread executes bytecode at a time. This is the single most common Python performance misunderstanding: adding threads to a CPU-bound loop and measuring no improvement.

6. Getting It Wrong

  • Processes for I/O-bound work. Hundreds of megabytes of process overhead to do what one event loop does in one process.
  • Threads for CPU-bound Python. No speedup, plus lock contention.
  • Unbounded concurrency. Ten thousand simultaneous requests exhausts file descriptors, floods the target, and gets you rate limited. Concurrency needs a semaphore.
  • Concurrency as a synonym for speed. If the bottleneck is the target's rate limit, more concurrency produces more 429s and no more data.

7. Real-World Examples

  • A scraper moved from sequential to async, going from 1 to 50 pages a second with no extra hardware, because the CPU was never the constraint.
  • A parsing stage moved to a process pool, using all cores where async had changed nothing.
  • A crawler that fell over at 5,000 concurrent requests, fixed by a semaphore capping in-flight work at 100.
  • A thread pool added to a CPU-bound Python loop, delivering no improvement and a lot of confusion.
  • A pipeline whose real limit was one request per second per host, where every concurrency change was irrelevant.

8. Summary

Concurrency structures work so tasks interleave while waiting; parallelism executes work simultaneously across cores. They are not degrees of the same thing.

Match the model to the bottleneck: I/O-bound work needs concurrency, CPU-bound work needs parallelism, and most real pipelines need both in different stages. In Python the GIL makes this a hard constraint rather than a preference β€” async or threads for I/O, processes for computation.

And bound your concurrency. Unlimited in-flight work exhausts local resources and overwhelms the target, which usually converts a throughput problem into a rate limiting one.