Python

Concurrency & Parallelism

45 flashcards · answers and spaced-repetition review in the KnowCard app

You submit two tasks; the first is slow, the second fast. You print f1.result() then f2.result(). Which result appears first?
You have a CPU-bound function (heavy number crunching). You run 4 copies across 4 threading.Threads on a 4-core machine. Does it finish ~4x faster?
If the GIL makes threads useless for speeding up CPU work, what ARE threads actually good for in CPython?
You need to share a large mutable dict between parallel workers and have them all update it live. Threads or processes?
What does this print? ``` import threading class C(threading.Thread): counter = 0 def run(self): for _ in range(2000000): C.counter += 1 a, b = C(), C() a.start(); b.start() a.join(); b.join() print(C.counter) ```
How do you fix the lost-update race on a shared counter incremented by multiple threads?
What's the practical difference between lock.acquire()/lock.release() and with lock: for a critical section?
Two threads each acquire two locks but in opposite order: A holds L then wants M; B holds M then wants L. What happens?
With threading.Thread, which method do you call to start the thread, and what actually runs?
Why does a ProcessPoolExecutor example wrap the launching code in if __name__ == "__main__":?
What does executor.submit(fn, arg) return, and when does the call return control to you?
An Executor used as a context manager — when does the with block exit?
What does this print, and in what order? ``` async def sp(n): print(f"wait {n}") await asyncio.sleep(n) print(f"done {n}") async def main(): await sp(3) await sp(1) asyncio.run(main()) ```
How do you actually run two coroutines concurrently (overlapping their awaits), not one-after-another?
What happens if you write await asyncio.sleep(1) at module top level (outside any async def)?
What does calling a coroutine function like sleep_print(5) actually do?
asyncio is concurrent — so is it safe to call time.sleep(5) (or any blocking call) inside a coroutine?
Why doesn't asyncio code need locks around shared data the way threading does?
What does asyncio.run(main()) do, and what's the rule about calling it more than once?
Your asyncio program exits but a coroutine you started never finished its work. You used asyncio.create_task(worker()) and never awaited it. Why?
For ProcessPoolExecutor.map(fn, big_list), why can passing chunksize=N speed things up — and does it matter for threads?
You're building a server that must handle thousands of concurrent I/O operations. Why might asyncio beat a thread/process pool here — and what's the catch that makes it a big commitment?
You've decided against asyncio and will use threads or processes. Which module should you reach for first, and why not threading/multiprocessing directly?
How does an executor's map(fn, iterable) differ from the built-in map(fn, iterable)?
In an asyncio producer/consumer crawler, each consumer loops forever on await queue.get(). How does the program know all work is done, and how do you then stop the idle consumers?
Why does aiofiles open a file with async with while ordinary file I/O just uses with?
On a single-core machine several programs seem to run at the same time, yet a CPU can execute only one task per instant. What actually produces that illusion?
How many threads does a freshly started process have, and what exactly is a thread in relation to its process?
Threads are nicknamed "lightweight processes." Compared with spawning another process, what makes a thread lighter?
Threads and processes are "preemptive" multitasking, while asyncio is "cooperative." What decides when a context switch happens in each?
Python offers three parallelization interfaces: concurrent.futures, threading/multiprocessing, and asyncio. How do they differ in abstraction and capability?
Is concurrent.futures a separate parallelism engine, and what kind of workload is it meant for?
You create ThreadPoolExecutor(max_workers=3) and immediately submit 4 tasks. When does the 4th task actually begin running?
By default executor.shutdown() blocks until every task finishes. What changes if you call shutdown(wait=False) instead — and can you still submit tasks afterward?
Calling executor.shutdown() lets already-running tasks finish. Which Python version, and which parameter, lets you also skip tasks that haven't started yet?
Why can't you use ProcessPoolExecutor from an interactive Python session on Windows?
You want to poll a batch of Futures at regular intervals and act on whichever have finished. Which concurrent.futures function fits, and what does it return?
You pass a timeout to as_completed(fs, timeout=5) but the slowest task takes 20s. What happens? And must all fs come from one executor?
Besides result(), a Future exposes cancel(), cancelled(), running(), and done(). What does each report, and what does cancel()'s return value mean?
A task submitted to an executor raised an exception. How do you retrieve that exception, and how do you get a callback to fire the moment any result is ready?
You want to time how long a parallel computation takes. Which time-module function is specifically intended for measuring runtimes?
Porting a threaded program to multiprocessing, you keep the Lock as a class attribute shared by all workers, like the thread version did. Why does it break, and what's the fix?
asyncio.Queue: what's the difference between put/get and put_nowait/get_nowait, and what does giving the queue a maxsize do?
You must call a blocking function from inside a coroutine — a heavy CPU calc, or an I/O library with no async version. Calling it directly freezes the whole event loop. What's the asyncio-friendly way?
A coroutine contains a yield statement instead of return. What has it become, and how do you consume it so other coroutines still get to run?

Start learning today

Free to start — download the app or use it in your browser.

Get it on App StoreGet it on Google Play