Giao diện
⚡ Async I/O Deep Dive Engine
The Engine Underneath: Hiểu cách Event Loop hoạt động — nền tảng của Nginx, Node.js, và mọi high-performance server.
Blocking vs Non-Blocking I/O
Blocking I/O (Traditional)
cpp
// ❌ BLOCKING - Thread bị block khi đợi data
char buffer[1024];
int bytes = recv(socket, buffer, sizeof(buffer), 0);
// Thread STOPS here until data arrives (could be seconds!)
process(buffer, bytes);┌─────────────────────────────────────────────────────────────────────────┐
│ BLOCKING I/O TIMELINE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Thread 1: ┌──────┐ ┌──────────────────────┐ ┌────────┐ │
│ │ Work │ ──────── │ WAITING for recv() │ ─ │ Process│ │
│ └──────┘ └──────────────────────┘ └────────┘ │
│ ↑ │
│ WASTED TIME │
│ (thread idle) │
│ │
│ 1000 connections = 1000 threads = 1000 × wasted time = 💀 │
│ │
└─────────────────────────────────────────────────────────────────────────┘Non-Blocking I/O (Modern)
cpp
// ✅ NON-BLOCKING - Check and return immediately
int flags = fcntl(socket, F_GETFL, 0);
fcntl(socket, F_SETFL, flags | O_NONBLOCK);
char buffer[1024];
int bytes = recv(socket, buffer, sizeof(buffer), 0);
if (bytes == -1 && errno == EAGAIN) {
// No data yet, do something else
// Event loop will notify when ready
}┌─────────────────────────────────────────────────────────────────────────┐
│ NON-BLOCKING I/O TIMELINE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Single Thread handling 1000 connections: │
│ │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Conn 1│ │Conn 5│ │Conn 3│ │Conn 7│ │Conn 2│ │Conn 8│ │Conn 1│ ... │
│ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
│ Event Loop: "Only process connections with ready data" │
│ → No wasted time, CPU always doing useful work │
│ │
└─────────────────────────────────────────────────────────────────────────┘The Event Loop — Visualized
┌─────────────────────────────────────────────────────────────────────────┐
│ EVENT LOOP ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ EVENT LOOP │ │
│ │ (io_context) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Timer │ │ Socket │ │ Signal │ │
│ │ Events │ │ Events │ │ Events │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
│ └─────────────┼─────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ OS Kernel │ │
│ │ (epoll/kqueue) │ │
│ └─────────────────┘ │
│ │
│ Loop Flow: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. Wait for events (epoll_wait) │ │
│ │ 2. Dispatch ready handlers │ │
│ │ 3. Execute handlers (callbacks) │ │
│ │ 4. Go to step 1 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘Boost.Asio — Industry Standard
Boost.Asio là thư viện async I/O mạnh nhất cho C++, được sử dụng trong:
- gRPC C++ implementation
- Beast (HTTP/WebSocket)
- Crow, Drogon (web frameworks)
- HPN Tunnel
Installation
bash
# Via Conan
conan install boost/1.83.0@
# Via Vcpkg
vcpkg install boost-asio
# Ubuntu
sudo apt install libboost-all-devCMake Integration
cmake
find_package(Boost REQUIRED COMPONENTS system)
target_link_libraries(myapp PRIVATE Boost::system)Asio Basics: io_context
io_context là trung tâm của Asio — Event Loop.
cpp
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
int main() {
// Create the event loop
asio::io_context io;
// Post work to the event loop
io.post([]() {
std::cout << "Hello from event loop!" << std::endl;
});
io.post([]() {
std::cout << "Another task!" << std::endl;
});
// Run the event loop (blocks until no more work)
io.run();
return 0;
}Timer Example
cpp
#include <boost/asio.hpp>
#include <iostream>
#include <chrono>
namespace asio = boost::asio;
using namespace std::chrono_literals;
int main() {
asio::io_context io;
// Create a timer
asio::steady_timer timer(io, 2s);
// Async wait - doesn't block!
timer.async_wait([](const boost::system::error_code& ec) {
if (!ec) {
std::cout << "Timer expired!" << std::endl;
}
});
std::cout << "Timer started, doing other work..." << std::endl;
// This runs until timer callback completes
io.run();
return 0;
}TCP Echo Server (Callback Style)
cpp
#include <boost/asio.hpp>
#include <iostream>
#include <memory>
namespace asio = boost::asio;
using asio::ip::tcp;
class Session : public std::enable_shared_from_this<Session> {
public:
Session(tcp::socket socket) : socket_(std::move(socket)) {}
void start() {
do_read();
}
private:
void do_read() {
auto self = shared_from_this();
socket_.async_read_some(
asio::buffer(data_, max_length),
[this, self](boost::system::error_code ec,
std::size_t length) {
if (!ec) {
do_write(length);
}
});
}
void do_write(std::size_t length) {
auto self = shared_from_this();
asio::async_write(
socket_,
asio::buffer(data_, length),
[this, self](boost::system::error_code ec,
std::size_t /*length*/) {
if (!ec) {
do_read(); // Continue reading
}
});
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class Server {
public:
Server(asio::io_context& io, short port)
: acceptor_(io, tcp::endpoint(tcp::v4(), port)) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(
[this](boost::system::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<Session>(std::move(socket))->start();
}
do_accept(); // Accept next connection
});
}
tcp::acceptor acceptor_;
};
int main() {
try {
asio::io_context io;
Server server(io, 8080);
std::cout << "Server running on port 8080" << std::endl;
io.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
}C++20 Coroutines (Modern Approach)
Callbacks = "Callback Hell". Coroutines = Clean, linear code.
cpp
// C++20 with Asio coroutines
#include <boost/asio.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
using asio::awaitable;
using asio::co_spawn;
using asio::detached;
using asio::use_awaitable;
awaitable<void> echo(tcp::socket socket) {
try {
char data[1024];
for (;;) {
// co_await - suspend until data ready
std::size_t n = co_await socket.async_read_some(
asio::buffer(data), use_awaitable);
// co_await - suspend until write complete
co_await async_write(socket,
asio::buffer(data, n),
use_awaitable);
}
} catch (std::exception& e) {
std::cout << "Connection closed: " << e.what() << std::endl;
}
}
awaitable<void> listener() {
auto executor = co_await asio::this_coro::executor;
tcp::acceptor acceptor(executor, {tcp::v4(), 8080});
for (;;) {
tcp::socket socket = co_await acceptor.async_accept(use_awaitable);
co_spawn(executor, echo(std::move(socket)), detached);
}
}
int main() {
try {
asio::io_context io;
co_spawn(io, listener(), detached);
io.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
}Comparison: Callbacks vs Coroutines
┌─────────────────────────────────────────────────────────────────────────┐
│ CALLBACKS vs COROUTINES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ CALLBACKS (Callback Hell): │
│ ───────────────────────── │
│ async_read([](ec, n) { │
│ if (!ec) { │
│ async_write([](ec, n) { │
│ if (!ec) { │
│ async_read([](ec, n) { │
│ // ... nested forever │
│ }); │
│ } │
│ }); │
│ } │
│ }); │
│ │
│ COROUTINES (Linear, clean): │
│ ─────────────────────────── │
│ auto n = co_await async_read(use_awaitable); │
│ co_await async_write(use_awaitable); │
│ n = co_await async_read(use_awaitable); │
│ // Reads like synchronous code! │
│ │
└─────────────────────────────────────────────────────────────────────────┘Multi-threaded Event Loop
cpp
#include <boost/asio.hpp>
#include <thread>
#include <vector>
int main() {
asio::io_context io;
// Prevent io_context from stopping when no work
auto work_guard = asio::make_work_guard(io);
// Create thread pool
std::vector<std::thread> threads;
const auto thread_count = std::thread::hardware_concurrency();
for (unsigned i = 0; i < thread_count; ++i) {
threads.emplace_back([&io]() {
io.run(); // Each thread runs the same io_context
});
}
// ... add your async operations ...
// Shutdown
work_guard.reset(); // Allow io_context to stop
for (auto& t : threads) {
t.join();
}
}┌─────────────────────────────────────────────────────────────────────────┐
│ MULTI-THREADED IO_CONTEXT │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ io_context │ │
│ │ (Event Loop) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ Thread 1 │ │ Thread 2 │ │ Thread N │ │
│ │ io.run() │ │ io.run() │ │ io.run() │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
│ All threads share the same io_context │
│ Handlers may run on ANY thread → Use strand for serialization │
│ │
└─────────────────────────────────────────────────────────────────────────┘Strand for Thread Safety
cpp
// strand ensures handlers are serialized (not concurrent)
asio::strand<asio::io_context::executor_type> strand(
asio::make_strand(io));
// All handlers on this strand run sequentially
asio::post(strand, []() {
// This handler...
});
asio::post(strand, []() {
// ...and this handler will NEVER run concurrently
});OS-Level Event Notification
┌─────────────────────────────────────────────────────────────────────────┐
│ OS EVENT NOTIFICATION MECHANISMS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Linux: epoll │
│ ───────────────── │
│ • O(1) event notification │
│ • Edge-triggered or Level-triggered │
│ • Scales to millions of connections │
│ │
│ macOS/BSD: kqueue │
│ ───────────────── │
│ • Similar to epoll │
│ • Supports file, socket, signal, timer events │
│ │
│ Windows: IOCP (I/O Completion Ports) │
│ ─────────────────────────────────────── │
│ • Proactor model (completion-based) │
│ • Kernel handles more work │
│ │
│ Linux 5.1+: io_uring │
│ ───────────────────── │
│ • Newest, fastest mechanism │
│ • Kernel bypass possible │
│ • Used in cutting-edge systems │
│ │
│ Boost.Asio abstracts ALL of these! Write once, run anywhere. │
│ │
└─────────────────────────────────────────────────────────────────────────┘Best Practices
┌─────────────────────────────────────────────────────────────────────────┐
│ ASYNC I/O BEST PRACTICES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ✅ DO │
│ ───── │
│ • Use shared_ptr with enable_shared_from_this for session lifetime │
│ • Use strand for thread-safe handler execution │
│ • Prefer coroutines (C++20) over raw callbacks │
│ • Pre-allocate buffers to avoid allocation in hot path │
│ • Use work_guard to prevent premature io_context exit │
│ │
│ ❌ DON'T │
│ ─────── │
│ • Don't block inside handlers (defeats the purpose!) │
│ • Don't forget error handling (always check error_code) │
│ • Don't share mutable state without synchronization │
│ • Don't use global io_context (prefer dependency injection) │
│ │
│ 🏎️ PERFORMANCE TIPS │
│ ───────────────────── │
│ • Thread count = CPU cores (not more!) │
│ • Edge-triggered epoll for very high throughput │
│ • Object pool for Session objects (avoid new/delete) │
│ │
└─────────────────────────────────────────────────────────────────────────┘