Choose threads, processes, or asyncio from workload shape, and use concurrent.futures without deadlocks. Use when parallelizing Python work or debugging concurrency that is slower than sequential.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add Amey-Thakur/AI-SKILLS --skill python-concurrency --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Python Concurrency?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/amey-thakur-python-concurrency)More formats (shields.io, HTML) on the badges page.
---
name: python-concurrency
description: Choose threads, processes, or asyncio from workload shape, and use concurrent.futures without deadlocks. Use when parallelizing Python work or debugging concurrency that is slower than sequential.
---
# Python concurrency
One decision drives everything: is the work waiting (I/O) or computing
(CPU)? Waiting scales with threads or asyncio; computing needs processes,
because the GIL serializes Python bytecode in one process.
## Method
1. **Classify the workload.** I/O-bound with few (< ~100) concurrent
operations: threads. I/O-bound with hundreds-plus concurrent operations:
asyncio. CPU-bound: processes, or a C-backed library that releases the
GIL (numpy does; your pure-Python loop does not). Mixed: processes for
the CPU stages, threads/async inside each for the I/O.
2. **Reach for concurrent.futures first.**
`ThreadPoolExecutor` / `ProcessPoolExecutor` with `executor.map` or
`as_completed` covers most fan-out. It beats raw `threading.Thread`
(join bookkeeping, no results) and raw `multiprocessing` (lifecycle
traps) for ordinary parallel work.
3. **Size pools from the resource.** Threads: bounded by what the remote
ends tolerate, not CPU; 8-32 is common. Processes: `os.cpu_count()` or
slightly under. Oversized process pools thrash memory since each worker
is a full interpreter.
4. **Mind pickling at the process boundary.** Arguments and returns must
pickle: no lambdas, no open sockets, no bound methods of unpicklable
objects. Chunk small tasks (`map(..., chunksize=...)`) or IPC overhead
eats the speedup.
5. **Share nothing; communicate results.** Between threads, guard any
shared mutable state with one lock or hand items over a
`queue.Queue`. Between processes, return values and let the parent
aggregate. Two locks acquired in different orders is a deadlock you
will meet in production, not in tests.
6. **Shut down deterministically.** Context-manage executors; on 3.9+ use
`shutdown(cancel_futures=True)` to abandon queued work on error. A
non-daemon thread blocked on a queue keeps the process alive forever.
## Boundaries
- Threads give zero speedup for pure-Python CPU work under the GIL; a
free-threaded (no-GIL) interpreter changes this but is not the default.
- Do not mix fork-based multiprocessing with threads already running;
forked locks inherit held state and hang. Use the spawn start method.
- For distributed scale (many machines) this skill stops; that is a job
queue or dataflow engine, not stdlib concurrency.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!