← Назад к вопросам

Зачем нужен системный вызов SELECT?

3.0 Senior🔥 81 комментариев
#FastAPI и Flask#Python Core#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Системный вызов SELECT: мониторинг I/O в Linux

select — это один из первых и наиболее фундаментальных системных вызовов для асинхронного I/O в Linux/Unix. Это не SQL SELECT! Это низкоуровневый механизм для мониторинга нескольких файловых дескрипторов одновременно.

Что такое SELECT?

select — это системный вызов, который ждёт, пока один или несколько файловых дескрипторов будут готовы к чтению, записи или получению исключения.

// C синтаксис (для контекста)
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

Возвращаемое значение:

  • Положительное число — количество готовых файловых дескрипторов
  • 0 — timeout истёк
  • -1 — ошибка

Проблема, которую SELECT решает

Представь, что нужно слушать несколько сокетов одновременно:

# Наивный подход БЕЗ select (ПЛОХО)
import socket

server1 = socket.socket()
server2 = socket.socket()
server3 = socket.socket()

server1.bind(('localhost', 8001))
server2.bind(('localhost', 8002))
server3.bind(('localhost', 8003))

# Но как слушать все три одновременно?
# Если делать просто accept() на server1, то server2 и server3 игнорируются!
while True:
    connection1, addr1 = server1.accept()  # Блокируется здесь
    # server2 и server3 не могут принимать соединения!

SELECT решает эту проблему:

import select
import socket

server1 = socket.socket()
server2 = socket.socket()
server3 = socket.socket()

server1.bind(('localhost', 8001))
server2.bind(('localhost', 8002))
server3.bind(('localhost', 8003))

server1.listen(1)
server2.listen(1)
server3.listen(1)

sockets = [server1, server2, server3]

while True:
    # Ждём пока ЛЮБОЙ сокет станет готов
    readable, writable, exceptional = select.select(sockets, [], sockets, timeout=5)
    
    # Один или несколько готовы!
    for sock in readable:
        if sock == server1:
            connection, addr = server1.accept()
            print(f"New connection on server1: {addr}")
        elif sock == server2:
            connection, addr = server2.accept()
            print(f"New connection on server2: {addr}")
        elif sock == server3:
            connection, addr = server3.accept()
            print(f"New connection on server3: {addr}")

SELECT в Python: модуль select

import select
import socket

# Создание простого echo сервера с select
def echo_server():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('localhost', 5000))
    server.listen(1)
    
    clients = []  # Список активных клиентов
    server_sockets = [server]  # Слушаем серверный сокет
    
    while True:
        # select ждёт пока сокеты станут готовы к чтению
        readable, _, _ = select.select(server_sockets + clients, [], [], 5)
        
        for sock in readable:
            if sock is server:
                # Новое соединение
                client_socket, address = server.accept()
                print(f"New client: {address}")
                clients.append(client_socket)
            else:
                # Данные от клиента
                try:
                    data = sock.recv(1024)
                    if data:
                        print(f"Received: {data.decode()}")
                        sock.send(data)  # Echo back
                    else:
                        # Клиент отключился
                        clients.remove(sock)
                        sock.close()
                except Exception as e:
                    print(f"Error: {e}")
                    clients.remove(sock)
                    sock.close()

echo_server()

SELECT vs EPOLL vs ASYNCIO

SELECT — старая технология, но всё ещё используется:

# 1. SELECT (старый, портативный, О(n) сложность)
import select

readable, writable, _ = select.select(sockets, [], [], timeout)
# Проверяет ВСЕ сокеты каждый раз — медленно при 10000+ соединений

# 2. EPOLL (новый, Linux только, О(1) сложность)
import select

epoll = select.epoll()  # Только на Linux
epoll.register(sock.fileno(), select.EPOLLIN)
events = epoll.poll(timeout)
for fd, event in events:
    # Получим только готовые сокеты
    print(f"Socket {fd} is ready")

# 3. ASYNCIO (высокоуровневый, современный)
import asyncio

async def handle_client(reader, writer):
    while True:
        data = await reader.read(1024)
        if not data:
            break
        writer.write(data)
        await writer.drain()

async def main():
    server = await asyncio.start_server(
        handle_client, 'localhost', 5000
    )
    await server.serve_forever()

asyncio.run(main())

Как SELECT работает под капотом

  1. Приложение передаёт список FD — "я жду готовности этих файловых дескрипторов"
  2. SELECT блокируется в ядре операционной системы
  3. Ядро ОС мониторит эти FD (используя interno device drivers)
  4. Когда один готов — SELECT пробуждается
  5. Возвращает набор готовых FD — приложение обрабатывает
import select
import sys

# Пример: мониторим stdin (клавиатуру) с timeout
while True:
    print("Waiting for input (5 sec timeout)...")
    
    readable, _, _ = select.select([sys.stdin], [], [], 5)
    
    if readable:
        line = sys.stdin.readline()
        print(f"You typed: {line}")
    else:
        print("Timeout! No input.")

SELECT на файлах (не только сокеты)

import select
import os

# SELECT работает не только с сокетами!
# Также с pipe, файлами, терминалами

# Пример: читаем из нескольких файлов
f1 = open('file1.txt', 'r')
f2 = open('file2.txt', 'r')

while True:
    readable, _, _ = select.select([f1, f2], [], [], 5)
    
    for f in readable:
        line = f.readline()
        if line:
            print(f"From {f.name}: {line.strip()}")

Практический пример: мониторинг нескольких источников

import select
import socket
import sys

def monitor_multiple_sources():
    # Серверный сокет
    server = socket.socket()
    server.bind(('localhost', 5000))
    server.listen(1)
    
    # Стандартный ввод
    stdin = sys.stdin
    
    # Мониторим оба: сокет и stdin
    sources = [server, stdin]
    
    while True:
        readable, _, _ = select.select(sources, [], [])
        
        for source in readable:
            if source is server:
                # Новое соединение на сокете
                client, addr = server.accept()
                print(f"\nClient connected: {addr}")
                client.send(b"Hello from server\n")
                client.close()
            
            elif source is stdin:
                # Ввод с клавиатуры
                line = stdin.readline()
                if line:
                    print(f"You typed: {line.strip()}")
                else:
                    print("EOF from stdin")
                    return

monitor_multiple_sources()

SELECT vs BLOCKING I/O

# BLOCKING I/O (одновременно один сокет)
sock.recv(1024)  # Блокируется, пока не придут данные
                 # Можем обрабатывать только один сокет!

# SELECT (асинхронный мониторинг)
readable, _, _ = select.select([sock1, sock2, sock3], [], [])
# Блокируется, пока ЛЮБОЙ не будет готов
# Обрабатываем только те, которые готовы

Когда использовать SELECT

✅ Используй SELECT когда:

  • Нужно слушать несколько сокетов одновременно
  • Пишешь сетевой сервер (но есть лучше — asyncio)
  • Нужна портативность (select работает везде: Linux, Windows, macOS)
  • Работаешь с legacy кодом

❌ Не используй SELECT в современном коде:

  • Используй asyncio — это более высокоуровнево и pythonic
  • На Linux можно использовать epoll для производительности
  • Для веб-серверов используй готовые фреймворки (FastAPI, Flask с gunicorn)

Заключение

select — это основа асинхронного I/O в Unix/Linux. Хотя в современном Python мы используем asyncio, важно понимать что находится под капотом:

  • SELECT мониторит множество файловых дескрипторов
  • Позволяет одному процессу обрабатывать многих клиентов
  • Является основой для веб-серверов
  • Был заменён на EPOLL (Linux), KQUEUE (macOS), IOCP (Windows)

В практическом программировании на Python используй asyncio, но понимание SELECT важно для глубокого понимания того как работают сетевые приложения.