Imagine checking whether a pizza is ready, then doing something else while it cooks. Non-blocking I/O works similarly: an I/O call returns without making the execution thread wait for a device or network connection. The application can work on another task and try again when the I/O is ready. section 10.11
This matters when I/O takes much longer than CPU work. A server waiting for one client's network data could instead attend to another client. One thread can therefore make progress on multiple I/O-bound requests without assigning a waiting thread to each connection. It does not make an individual I/O operation complete sooner. section 10.0 section 10.12
A socket that is not ready
In Python, setblocking(False) puts a socket in non-blocking mode. A call such as recv() cannot wait for data: if none is available, it raises BlockingIOError. The application can handle that exception, serve another connection, and retry later. The exception means “not ready now,” not necessarily “the connection has failed.” section 10.11
# Chapter 10/pizza_busy_wait.py
import typing as T
from socket import socket, create_server
BUFFER_SIZE = 1024
ADDRESS = ("127.0.0.1", 12345)
class Server:
clients: T.Set[socket] = set()
def __init__(self) -> None:
try:
print(f"Starting up at: {ADDRESS}")
self.server_socket = create_server(ADDRESS)
self.server_socket.setblocking(False) #1
except OSError:
self.server_socket.close()
print("\nServer stopped.")
def accept(self) -> None:
try:
conn, address = self.server_socket.accept()
print(f"Connected to {address}")
conn.setblocking(False) #1
self.clients.add(conn)
except BlockingIOError: #2
pass #2
def serve(self, conn: socket) -> None:
try:
while True:
data = conn.recv(BUFFER_SIZE)
if not data:
break
try:
order = int(data.decode())
response = f"Thank you for ordering {order} pizzas!\n"
except ValueError:
response = "Wrong number of pizzas, please try again\n"
print(f"Sending message to {conn.getpeername()}")
conn.send(response.encode())
except BlockingIOError: #2
pass #2
def start(self) -> None:
print("Server listening for incoming connections")
try:
while True:
self.accept()
for conn in self.clients.copy():
self.serve(conn)
finally:
self.server_socket.close()
print("\nServer stopped.")
if __name__ == "__main__":
server = Server()
server.start()
The server listing puts its listening and accepted sockets in non-blocking mode and handles BlockingIOError when accept() has no connection ready. section 10.11
Repeatedly retrying every socket in an endless loop is called busy-waiting. It avoids blocking but can waste processor time checking for work that is not there. A reactor-style approach instead uses a notification call such as select to wait for I/O status, then handles sockets that are ready. section 11.10 section 11.11
Non-blocking and asynchronous describe different things. Non-blocking describes how an I/O call returns; asynchronous describes how the application continues and later obtains a result. A non-blocking call may require repeated readiness checks. In an asynchronous non-blocking model, an initiated operation can instead finish in the background and produce a signal or callback. section 11.8 section 11.12




