π Chapter 1: Getting to Know Asyncio
π What is
π₯οΈ I/O-bound vs. CPU-bound Tasks
- I/O-bound: Tasks that wait for input/output, such as web requests or database queries. These tasks benefit greatly from
- CPU-bound: Tasks that use lots of computational power (like math calculations).
βοΈ Concurrency, Parallelism, and Multitasking
- Concurrency: When multiple tasks appear to be running at the same time by taking turns. For example, while one task is waiting for a file to download, another task can start.
- Parallelism: When tasks are literally running at the same time on multiple CPU cores.
- Multitasking: Managing several tasks at once. This can be preemptive (the OS decides when to switch between tasks) or cooperative (tasks decide when to yield control).
π Processes vs. Threads
- Processes: Independent units that do not share memory. They are good for CPU-bound tasks but use more resources.
- Threads: Lighter-weight than processes and share memory within the same program. However, Pythonβs Global Interpreter Lock (GIL) prevents threads from running Python code at the same time (no parallel execution). Still, threads are useful for I/O-bound tasks.
π Global Interpreter Lock (GIL)
- GIL is a mechanism in Python that allows only one thread to execute Python bytecode at a time, even on multi-core systems.
- This makes multithreading less useful for CPU-bound operations but still beneficial for I/O-bound tasks because I/O releases the GIL.
β‘ Single-threaded Concurrency
π¬ What is a Socket?
A socket is a low-level connection to send and receive data over a network. By default, sockets are blocking, meaning they make the program wait while they communicate. Non-blocking sockets allow the program to continue executing other tasks while waiting for a response. This is key to how
π Event Loop
- An event loop is a core part of how
- Tasks that involve I/O can pause and free up the loop to run other tasks, making programs more efficient.
πΉοΈ How It Works:
1. Tasks are submitted to the event loop.
2. The loop starts running tasks. If a task hits an I/O operation (like a web request), the task pauses.
3. The operating system watches the socket and informs the event loop when the I/O is complete.
4. The event loop resumes the paused task and continues processing.
π€ Why Use
- Improves performance for programs that do a lot of waiting (web servers, file reading).
- Lightweight and doesnβt need multiple threads or processes to handle concurrency.
- Efficient resource utilization: While waiting for slow I/O operations, the CPU can continue working on other tasks, leading to faster overall execution.
π Key Takeaways:
-
- It does not remove Python's GIL but makes it less of an issue by focusing on I/O tasks.
- Non-blocking I/O and the event loop allow tasks to pause and resume, making concurrency efficient even with just one thread.
#PythonConcurrencyWithAsyncio
#Chapter_01
#Notes #Book
π What is
asyncio?asyncio is a Python library introduced in version 3.4 that allows you to run I/O-bound tasks concurrently. Rather than making your code wait for slow operations, asyncio allows multiple tasks to run "in parallel" by pausing tasks when theyβre waiting for I/O. This allows Python to work on other tasks in the meantime.π₯οΈ I/O-bound vs. CPU-bound Tasks
- I/O-bound: Tasks that wait for input/output, such as web requests or database queries. These tasks benefit greatly from
asyncio as it allows them to pause and let other tasks run while waiting.- CPU-bound: Tasks that use lots of computational power (like math calculations).
asyncio isn't designed for CPU-heavy tasks.βοΈ Concurrency, Parallelism, and Multitasking
- Concurrency: When multiple tasks appear to be running at the same time by taking turns. For example, while one task is waiting for a file to download, another task can start.
- Parallelism: When tasks are literally running at the same time on multiple CPU cores.
- Multitasking: Managing several tasks at once. This can be preemptive (the OS decides when to switch between tasks) or cooperative (tasks decide when to yield control).
asyncio uses cooperative multitasking, meaning tasks "cooperate" by pausing when they reach I/O.π Processes vs. Threads
- Processes: Independent units that do not share memory. They are good for CPU-bound tasks but use more resources.
- Threads: Lighter-weight than processes and share memory within the same program. However, Pythonβs Global Interpreter Lock (GIL) prevents threads from running Python code at the same time (no parallel execution). Still, threads are useful for I/O-bound tasks.
π Global Interpreter Lock (GIL)
- GIL is a mechanism in Python that allows only one thread to execute Python bytecode at a time, even on multi-core systems.
- This makes multithreading less useful for CPU-bound operations but still beneficial for I/O-bound tasks because I/O releases the GIL.
β‘ Single-threaded Concurrency
asyncio achieves concurrency without needing multiple threads by using non-blocking I/O and an event loop. π¬ What is a Socket?
A socket is a low-level connection to send and receive data over a network. By default, sockets are blocking, meaning they make the program wait while they communicate. Non-blocking sockets allow the program to continue executing other tasks while waiting for a response. This is key to how
asyncio achieves concurrency with just one thread.π Event Loop
- An event loop is a core part of how
asyncio works. Itβs a loop that runs tasks, pausing and resuming them as necessary. The event loop checks if tasks are waiting on I/O and either runs them or pauses them until theyβre ready.- Tasks that involve I/O can pause and free up the loop to run other tasks, making programs more efficient.
πΉοΈ How It Works:
1. Tasks are submitted to the event loop.
2. The loop starts running tasks. If a task hits an I/O operation (like a web request), the task pauses.
3. The operating system watches the socket and informs the event loop when the I/O is complete.
4. The event loop resumes the paused task and continues processing.
π€ Why Use
asyncio?- Improves performance for programs that do a lot of waiting (web servers, file reading).
- Lightweight and doesnβt need multiple threads or processes to handle concurrency.
- Efficient resource utilization: While waiting for slow I/O operations, the CPU can continue working on other tasks, leading to faster overall execution.
π Key Takeaways:
-
asyncio is ideal for I/O-bound tasks and allows you to write concurrent programs using a single thread.- It does not remove Python's GIL but makes it less of an issue by focusing on I/O tasks.
- Non-blocking I/O and the event loop allow tasks to pause and resume, making concurrency efficient even with just one thread.
#PythonConcurrencyWithAsyncio
#Chapter_01
#Notes #Book
β€4π1
π Chapter 2: *Asyncio Basics* π
Chapter 2 delves into the foundational aspects of asyncio in Python, focusing on how it enables single-threaded concurrency using coroutines, tasks, and event loops. Hereβs a breakdown:
π 2.1 Introducing Coroutines
- Coroutines are special Python functions that can pause and resume execution when encountering a potentially long-running task.
- When a coroutine pauses to wait for an operation, other tasks can run concurrently, providing concurrency. π‘
- `async` and `await` are the two essential keywords:
-
-
βοΈ Example of Creating a Coroutine
This is similar to a normal Python function but can pause its execution.
β³ 2.2 Introducing Long-Running Coroutines with `sleep`
- Asyncioβs
- When
βοΈ Example of Using
This coroutine pauses for 1 second before returning "Hello World!". During that second, other coroutines can run concurrently.
π 2.3 Running Concurrently with Tasks
- A task is a wrapper around a coroutine that schedules it to run on the event loop π.
- Tasks allow coroutines to be run concurrently, as they donβt block the event loop, unlike
βοΈ Example of Creating a Task
Here, the task
β 2.4 Canceling Tasks and Setting Timeouts
- Tasks can be canceled using
βοΈ Example of Cancelling a Task
πΌ 2.5 Tasks, Coroutines, Futures, and Awaitables
- Futures represent a value that will be available in the future but might not exist yet. They are used internally in asyncio and can be awaited π―.
- Coroutines and tasks can both be used in
βοΈ Example of Working with Futures
β±οΈ 2.6 Measuring Coroutine Execution Time with Decorators
- By using decorators, we can measure the execution time of coroutines for performance analysis. β
β οΈ 2.7 Pitfalls of Coroutines and Tasks
- Be cautious with CPU-bound code inside coroutines, as it will block the event loop.
- Avoid using blocking I/O APIs; use asyncio-compatible libraries to ensure the event loop runs smoothly π οΈ.
π§ 2.8 Accessing and Manually Managing the Event Loop
- You can access the event loop directly using
π οΈ 2.9 Using Debug Mode
- Debug mode helps in identifying long-running coroutines or tasks that block the event loop π§. You can enable it with:
Or by setting the
βοΈ Example of Running in Debug Mode
#PythonConcurrencyWithAsyncio
#Chapter_02
#Notes #Book
Chapter 2 delves into the foundational aspects of asyncio in Python, focusing on how it enables single-threaded concurrency using coroutines, tasks, and event loops. Hereβs a breakdown:
π 2.1 Introducing Coroutines
- Coroutines are special Python functions that can pause and resume execution when encountering a potentially long-running task.
- When a coroutine pauses to wait for an operation, other tasks can run concurrently, providing concurrency. π‘
- `async` and `await` are the two essential keywords:
-
async defines a function as a coroutine.-
await pauses the coroutine until a result is available from an asynchronous operation.βοΈ Example of Creating a Coroutine
async def my_coroutine() -> None:
print("Hello world!")
This is similar to a normal Python function but can pause its execution.
β³ 2.2 Introducing Long-Running Coroutines with `sleep`
- Asyncioβs
sleep function allows us to pause execution, simulating real-world, long-running operations like web requests or database queries π.- When
await asyncio.sleep() is called, other tasks can be executed during the pause.βοΈ Example of Using
asyncio.sleepasync def hello_world_message() -> str:
await asyncio.sleep(1)
return "Hello World!"
This coroutine pauses for 1 second before returning "Hello World!". During that second, other coroutines can run concurrently.
π 2.3 Running Concurrently with Tasks
- A task is a wrapper around a coroutine that schedules it to run on the event loop π.
- Tasks allow coroutines to be run concurrently, as they donβt block the event loop, unlike
await, which pauses until a result is returned.βοΈ Example of Creating a Task
import asyncio
from util import delay
async def main():
task = asyncio.create_task(delay(3))
await task
Here, the task
delay(3) runs concurrently, while other code can execute.β 2.4 Canceling Tasks and Setting Timeouts
- Tasks can be canceled using
task.cancel(), raising a CancelledError within the task. If a task is taking too long, we can also set timeouts using asyncio.wait_for π.βοΈ Example of Cancelling a Task
async def cancel_task(task):
task.cancel()
try:
await task
except asyncio.CancelledError:
print("Task was cancelled")
πΌ 2.5 Tasks, Coroutines, Futures, and Awaitables
- Futures represent a value that will be available in the future but might not exist yet. They are used internally in asyncio and can be awaited π―.
- Coroutines and tasks can both be used in
await expressions.βοΈ Example of Working with Futures
from asyncio import Future
my_future = Future()
my_future.set_result(42)
print(my_future.result()) # Outputs: 42
β±οΈ 2.6 Measuring Coroutine Execution Time with Decorators
- By using decorators, we can measure the execution time of coroutines for performance analysis. β
β οΈ 2.7 Pitfalls of Coroutines and Tasks
- Be cautious with CPU-bound code inside coroutines, as it will block the event loop.
- Avoid using blocking I/O APIs; use asyncio-compatible libraries to ensure the event loop runs smoothly π οΈ.
π§ 2.8 Accessing and Manually Managing the Event Loop
- You can access the event loop directly using
asyncio.get_event_loop(), though asyncio.run() is recommended for running main coroutines.π οΈ 2.9 Using Debug Mode
- Debug mode helps in identifying long-running coroutines or tasks that block the event loop π§. You can enable it with:
python3 -X dev program.py
Or by setting the
PYTHONASYNCIODEBUG environment variable.βοΈ Example of Running in Debug Mode
asyncio.run(main(), debug=True)
#PythonConcurrencyWithAsyncio
#Chapter_02
#Notes #Book
β€3π2