I/O transfers data between a program and an external resource, such as a keyboard, hard drive, or network card. Because these operations may wait for input or a web response, synchronous code can block the application and make it unresponsive. section 1.1
Concurrency lets an application handle multiple I/O operations together, such as simultaneous web requests. asyncio provides an asynchronous programming model for this, while threads can also overlap I/O because Python releases the GIL during those operations. section 1.1 section 1.5.1
Reading two web responses
Reading example.com twice synchronously takes about 0.2306 seconds in the demonstrated run. Starting one thread per request reduces the example to about 0.0977 seconds, although network conditions affect results. section 1.5.1
Listing 1.7 Synchronously reading status codes
import time
import requests
def read_example() -> None:
response = requests.get('https:/ / www .example .com')
print(response.status_code)
sync_start = time.time()
read_example()
read_example()
sync_end = time.time()
print(f'Running synchronously took {sync_end - sync_start:.4f} seconds.')
Compare the synchronous status-code example with its multithreaded counterpart to see how waiting on network I/O affects elapsed time. section 1.5.1



