Overview
Blocking and non-blocking execution models define how programs handle I/O operations and external resources. In blocking execution, program flow halts until an operation completes. The calling thread suspends execution and waits for the result before continuing. In non-blocking execution, operations return immediately, and the program continues executing while the operation completes in the background.
The distinction matters primarily for I/O-bound operations: reading files, making network requests, querying databases, or interacting with external services. CPU-bound operations execute synchronously regardless of the execution model, as computation cannot proceed until calculations complete.
Blocking operations dominate traditional programming models because they align with sequential reasoning. A program reads a file, the read completes, the program processes the data. This linear flow matches how developers think about program execution. Non-blocking operations emerged from the need to handle multiple concurrent I/O operations efficiently without dedicating a thread to each waiting operation.
# Blocking file read
content = File.read('data.txt')
process(content)
# Execution waits at File.read until entire file loads
# Non-blocking approach with async/await pattern
async_read('data.txt') do |content|
process(content)
end
continue_execution
# Execution continues immediately, callback runs when read completes
The performance implications differ dramatically at scale. A blocking web server handling 1000 concurrent requests requires 1000 threads, each consuming memory for its stack. A non-blocking server handles the same load with a small number of threads by multiplexing I/O operations. However, non-blocking code introduces complexity through callbacks, promises, or async/await patterns that complicate error handling and debugging.
Key Principles
Blocking execution follows a synchronous model where operations execute sequentially. When code invokes a blocking operation, the operating system deschedules the thread and places it in a wait queue. The thread remains suspended until the operation signals completion, then the OS reschedules the thread for execution. This mechanism enables cooperative multitasking at the OS level but creates overhead when many threads wait simultaneously.
The blocking model guarantees operation completion before continuing. After socket.read returns, the data exists in memory. After File.write completes, the OS has committed data to disk buffers. This guarantee simplifies error handling because failures occur at the call site. If the operation fails, the exception or error code appears exactly where the program invoked the operation.
Non-blocking execution inverts this model. Operations return immediately with a promise or future representing eventual completion. The operation proceeds asynchronously while the program continues executing. Three primary mechanisms deliver results: callbacks, promises/futures, or async/await syntax. Each mechanism addresses the same fundamental challenge: how to execute code after an asynchronous operation completes.
Callbacks represent the simplest mechanism. The caller provides a function that executes when the operation completes:
http_client.get('https://api.example.com') do |response|
parse_response(response)
end
Promises and futures provide a more structured approach. The operation returns an object representing the eventual result. Code can chain operations on this object, compose multiple asynchronous operations, or block waiting for completion:
promise = http_client.get('https://api.example.com')
promise.then { |response| parse_response(response) }
.then { |data| store_in_database(data) }
.catch { |error| handle_error(error) }
Event loops coordinate non-blocking operations. The loop monitors multiple I/O operations simultaneously using system calls like select, poll, epoll, or kqueue. When any operation completes, the event loop invokes the associated callback. This single-threaded model eliminates context switching overhead and synchronization complexity but requires that callbacks execute quickly to avoid blocking the event loop.
The distinction between blocking and non-blocking affects error handling fundamentally. Blocking operations raise exceptions at the call site. Non-blocking operations must propagate errors through the callback, promise, or async mechanism. An uncaught exception in a callback terminates the event loop or goes unnoticed, depending on the implementation:
# Blocking error handling
begin
data = File.read('config.json')
config = JSON.parse(data)
rescue Errno::ENOENT
use_default_config
rescue JSON::ParserError => e
log_error("Invalid config: #{e.message}")
end
# Non-blocking error handling
async_read('config.json')
.then { |data| JSON.parse(data) }
.catch(Errno::ENOENT) { use_default_config }
.catch(JSON::ParserError) { |e| log_error("Invalid config: #{e.message}") }
Resource management differs between models. Blocking code uses deterministic cleanup with ensure blocks or resource acquisition patterns. The linear control flow guarantees cleanup code executes. Non-blocking code must track resource lifetimes across asynchronous operations and ensure cleanup occurs after all callbacks complete.
Ruby Implementation
Ruby's Global Interpreter Lock (GIL) constrains non-blocking implementation strategies. The GIL prevents true parallel execution of Ruby code across multiple threads, but releases the lock during I/O operations. This design makes blocking I/O operations safe for concurrent execution while limiting CPU parallelism.
Ruby's standard library provides blocking I/O by default. Methods like File.read, Net::HTTP.get, and TCPSocket#read block the calling thread until the operation completes. Ruby releases the GIL during these operations, allowing other Ruby threads to execute concurrently:
threads = 10.times.map do |i|
Thread.new do
data = Net::HTTP.get(URI("https://api.example.com/data/#{i}"))
process_data(data)
end
end
threads.each(&:join)
# All requests execute concurrently despite blocking operations
Fibers provide lightweight cooperative concurrency. Unlike threads, fibers require explicit yielding and do not incur OS-level context switching overhead. Libraries build non-blocking abstractions on fibers by yielding when operations would block:
require 'fiber'
def async_read(file_path)
Fiber.new do
# Simulated async read
EventLoop.register_read(file_path) do |data|
Fiber.yield(data)
end
end
end
fiber = async_read('data.txt')
fiber.resume # Start the read
data = fiber.resume # Get the result
The async gem provides comprehensive non-blocking I/O for Ruby. It wraps blocking operations with fiber-based scheduling, automatically yielding when operations would block and resuming when data arrives:
require 'async'
require 'async/http'
Async do
internet = Async::HTTP::Internet.new
# Multiple concurrent requests without threads
responses = [
internet.get("https://example.com/1"),
internet.get("https://example.com/2"),
internet.get("https://example.com/3")
]
responses.each do |response|
puts response.read
end
ensure
internet.close
end
EventMachine provided Ruby's first popular non-blocking framework. It implements an event loop pattern where operations register callbacks that execute on completion. EventMachine operates in a single thread but handles thousands of concurrent operations:
require 'eventmachine'
EM.run do
http = EM::HttpRequest.new('https://api.example.com/data').get
http.callback do
puts http.response
EM.stop
end
http.errback do
puts "Request failed"
EM.stop
end
end
Celluloid provided actor-based concurrency, abstracting threads and enabling non-blocking patterns through message passing. Each actor processes messages sequentially but multiple actors run concurrently:
require 'celluloid/current'
class DataFetcher
include Celluloid
def fetch(url)
# Blocking operation runs in actor's thread
Net::HTTP.get(URI(url))
end
end
pool = DataFetcher.pool(size: 10)
futures = urls.map { |url| pool.future.fetch(url) }
results = futures.map(&:value) # Blocks until all complete
Ruby 3.0 introduced Ractor for parallel execution without the GIL. Ractors provide true parallelism but require message passing for communication, as they cannot share most objects:
ractor = Ractor.new do
data = Ractor.receive
# Process data in parallel
result = expensive_computation(data)
result
end
ractor.send(input_data)
result = ractor.take # Blocks until computation completes
The nio4r gem provides cross-platform non-blocking I/O primitives. It wraps platform-specific system calls like epoll and kqueue in a consistent interface:
require 'nio'
selector = NIO::Selector.new
socket = TCPSocket.new('example.com', 80)
monitor = selector.register(socket, :w)
selector.select(1) # Wait up to 1 second for writeable socket
if monitor.writeable?
socket.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
end
Non-blocking database access requires specialized gems. The mysql2 gem provides async query execution when used with an event loop:
require 'mysql2'
require 'eventmachine'
EM.run do
client = Mysql2::EM::Client.new(host: 'localhost', username: 'root')
query = client.query("SELECT * FROM users")
query.callback do |result|
result.each { |row| puts row['name'] }
EM.stop
end
end
Practical Examples
A web scraper demonstrates the performance difference between blocking and non-blocking approaches. The blocking version processes URLs sequentially, waiting for each response before requesting the next:
# Blocking scraper
require 'net/http'
urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3'
]
results = urls.map do |url|
uri = URI(url)
response = Net::HTTP.get(uri)
extract_data(response)
end
# Total time: sum of all request times
The non-blocking version issues all requests concurrently and processes responses as they arrive:
# Non-blocking scraper with async gem
require 'async'
require 'async/http/internet'
Async do
internet = Async::HTTP::Internet.new
tasks = urls.map do |url|
Async do
response = internet.get(url)
extract_data(response.read)
end
end
results = tasks.map(&:wait)
ensure
internet.close
end
# Total time: maximum of all request times (concurrent execution)
A chat server illustrates connection management differences. The blocking version dedicates one thread per connection:
# Blocking chat server
require 'socket'
server = TCPServer.new(3000)
loop do
client = server.accept # Blocks waiting for connection
Thread.new(client) do |conn|
loop do
message = conn.gets # Blocks waiting for message
break if message.nil?
broadcast(message)
end
conn.close
end
end
# Scales to ~1000 connections before thread overhead dominates
The non-blocking version multiplexes connections on a single thread:
# Non-blocking chat server
require 'socket'
require 'nio'
server = TCPServer.new(3000)
selector = NIO::Selector.new
clients = {}
selector.register(server, :r)
loop do
selector.select do |monitor|
if monitor.io == server
client = server.accept
selector.register(client, :r)
clients[client] = ""
else
data = monitor.io.read_nonblock(1024, exception: false)
if data == :wait_readable
next
elsif data.nil?
selector.deregister(monitor.io)
clients.delete(monitor.io)
monitor.io.close
else
clients[monitor.io] << data
broadcast(data) if data.include?("\n")
end
end
end
end
# Scales to 10,000+ connections with minimal memory overhead
Background job processing shows how blocking operations integrate with non-blocking event loops. A job processor might execute blocking database queries within an async context:
require 'async'
require 'async/barrier'
class JobProcessor
def initialize(queue)
@queue = queue
end
def process_jobs
Async do |task|
barrier = Async::Barrier.new
loop do
job = @queue.pop # Blocking operation
barrier.async do
# Execute job in separate fiber
execute_job(job)
end
# Limit concurrent jobs
barrier.wait if barrier.size >= 10
end
ensure
barrier.wait # Complete remaining jobs
end
end
private
def execute_job(job)
# Blocking database query
result = Database.query(job.sql)
job.callback(result)
end
end
File processing pipelines benefit from non-blocking I/O when reading multiple files concurrently:
require 'async'
require 'async/semaphore'
def process_files(file_paths)
Async do
semaphore = Async::Semaphore.new(5) # Limit concurrent reads
tasks = file_paths.map do |path|
semaphore.async do
data = File.read(path) # Ruby releases GIL during read
process_data(data)
end
end
tasks.map(&:wait)
end
end
API clients often mix blocking and non-blocking operations. The client uses non-blocking HTTP requests but blocks waiting for results at strategic points:
require 'async'
require 'async/http/internet'
class APIClient
def initialize
@internet = Async::HTTP::Internet.new
end
def fetch_user_data(user_id)
Async do |task|
# Issue parallel requests
profile_task = task.async { fetch_profile(user_id) }
posts_task = task.async { fetch_posts(user_id) }
friends_task = task.async { fetch_friends(user_id) }
# Block waiting for all results
{
profile: profile_task.wait,
posts: posts_task.wait,
friends: friends_task.wait
}
end
end
private
def fetch_profile(user_id)
response = @internet.get("https://api.example.com/users/#{user_id}")
JSON.parse(response.read)
end
end
Design Considerations
The choice between blocking and non-blocking execution depends on workload characteristics, scaling requirements, and development complexity tolerance. Blocking execution suits applications with predictable load, where thread-per-request overhead remains acceptable. Non-blocking execution becomes necessary when connection count exceeds available threads or when I/O latency dominates execution time.
Request patterns determine model suitability. Applications serving long-polling connections, websockets, or server-sent events maintain thousands of idle connections. Blocking models assign threads to these idle connections, consuming memory without performing work. Non-blocking models multiplex idle connections efficiently, checking for activity only when events occur.
CPU-bound versus I/O-bound workload ratios affect the decision. Pure I/O workloads benefit maximally from non-blocking execution because threads spend most time waiting. Mixed workloads with substantial CPU processing see diminishing returns, as non-blocking execution cannot parallelize computation without multiple threads or processes. When 80% of request time involves computation, the 20% I/O time saved through non-blocking execution provides minimal overall improvement.
Latency requirements influence model selection. Blocking execution introduces thread scheduling delays but maintains predictable per-request latency. Non-blocking execution reduces contention for threads but introduces queuing delays when the event loop falls behind. A system requiring 99th percentile latency under 50ms might choose blocking execution to avoid event loop congestion. A system optimizing for throughput over latency typically chooses non-blocking execution.
Memory constraints favor non-blocking execution. Each thread requires stack space, typically 1-2MB. Supporting 10,000 concurrent connections demands 10-20GB for thread stacks alone. Non-blocking execution handles the same connection count with a fixed number of threads, often 1-8, consuming minimal memory for connection state.
Development complexity increases substantially with non-blocking code. Debugging async code requires tracking execution across callbacks or promise chains. Stack traces become fragmented across event loop iterations. Testing requires mock event loops and careful coordination of async operations. Teams without async experience face a steep learning curve that can delay development significantly.
Library compatibility matters. Ruby gems expect blocking I/O. Using these gems in non-blocking code blocks the event loop, negating concurrency benefits. Applications requiring specific gems might need to wrap them in thread pools, hybrid blocking/non-blocking architectures, or seek async-compatible alternatives.
Error handling complexity multiplies in non-blocking code. Exceptions thrown in callbacks might not propagate to callers. Uncaught exceptions can terminate event loops silently. Implementing proper error boundaries, timeout handling, and error recovery requires careful planning. Blocking code handles errors with standard exception mechanisms and ensure blocks.
Migration costs influence decisions. Converting blocking codebases to non-blocking execution requires rewriting I/O operations, restructuring control flow, and implementing async patterns throughout. Incremental migration strategies exist but introduce architectural complexity. Starting new projects with non-blocking execution proves easier than migrating existing codebases.
Performance Considerations
Blocking execution scales linearly with connection count until thread limits appear. Each connection consumes thread resources: stack memory, kernel structures, and context switching overhead. Systems handle hundreds of concurrent connections comfortably. Thousands of connections cause thrashing as the OS spends more time context switching than executing application code.
Non-blocking execution scales differently. Connection count affects memory usage only for connection state, not thread stacks. A single event loop handles tens of thousands of connections, limited by event loop efficiency and application callback execution time rather than threading overhead. This scaling profile makes non-blocking execution mandatory for high-concurrency scenarios.
Throughput characteristics differ fundamentally. Blocking servers achieve throughput proportional to thread count times per-request processing speed. Doubling threads doubles throughput until contention appears. Non-blocking servers achieve throughput proportional to event loop efficiency times callback execution speed. Adding threads to non-blocking servers provides minimal benefit unless callbacks block or perform CPU-intensive work.
Latency patterns show different behavior. Blocking execution provides consistent per-request latency with occasional spikes during garbage collection or context switching. 99th percentile latency remains close to median latency. Non-blocking execution provides lower median latency but higher tail latency when the event loop accumulates backlog. 99th percentile latency can be 10x median latency during load spikes.
CPU utilization patterns reveal efficiency differences. Blocking servers show high CPU usage during request processing and low usage during I/O waits. The average CPU utilization appears moderate despite individual threads alternating between full utilization and idle. Non-blocking servers maintain consistent CPU usage, as the event loop continuously processes available work without idle periods.
Memory bandwidth affects both models differently. Blocking threads require memory for stacks and frequent cache invalidation during context switches. Non-blocking execution maintains better cache locality as a single thread processes multiple operations, but large connection counts stress memory bandwidth loading connection state.
Benchmark data illustrates practical differences:
# Benchmark blocking HTTP server
require 'benchmark'
require 'webrick'
server = WEBrick::HTTPServer.new(Port: 8000)
server.mount_proc('/') { |req, res| res.body = 'Hello' }
# Apache Bench results:
# 100 concurrent connections: 5,000 req/sec
# 1,000 concurrent connections: 4,000 req/sec (thread contention)
# 10,000 concurrent connections: server crashes (too many threads)
# Benchmark non-blocking HTTP server
require 'async'
require 'async/http/server'
server = Async::HTTP::Server.new do |request|
Protocol::HTTP::Response[200, {}, ['Hello']]
end
# Apache Bench results:
# 100 concurrent connections: 8,000 req/sec
# 1,000 concurrent connections: 10,000 req/sec
# 10,000 concurrent connections: 12,000 req/sec (scales well)
Database query performance demonstrates I/O wait impact. Blocking execution with sequential queries:
# Blocking sequential queries
start = Time.now
users = User.find([1, 2, 3, 4, 5])
profiles = users.map { |u| Profile.find(u.profile_id) }
elapsed = Time.now - start
# Total time: 5 * query_latency (assuming 20ms queries = 100ms total)
Non-blocking parallel queries:
# Non-blocking parallel queries
Async do
users = User.find([1, 2, 3, 4, 5])
tasks = users.map do |u|
Async { Profile.find(u.profile_id) }
end
profiles = tasks.map(&:wait)
end
# Total time: max(query_latencies) ≈ 20ms (all queries concurrent)
Optimization strategies differ between models. Blocking servers optimize through thread pool tuning, connection limits, and request queuing. Non-blocking servers optimize through callback efficiency, event loop tuning, and minimizing blocking operations within callbacks.
Common Pitfalls
Blocking the event loop destroys non-blocking performance. Any blocking operation within a callback prevents the event loop from processing other operations. A single 100ms blocking operation in a callback reduces throughput to 10 requests/second regardless of concurrent connection count:
# Event loop blocked - performance disaster
Async do
http = Async::HTTP::Internet.new
http.get('https://api.example.com') do |response|
# This blocks the event loop for 1 second
sleep(1)
process(response)
end
# No other operations can proceed during sleep
end
The solution wraps blocking operations in separate threads or processes:
# Proper handling of blocking operations
Async do
http = Async::HTTP::Internet.new
http.get('https://api.example.com') do |response|
# Execute blocking operation in thread pool
Async do |task|
task.async do
Thread.new { process(response) }.value
end
end
end
end
Error propagation fails silently in callbacks without explicit handling. Unhandled exceptions in callbacks might terminate the event loop or disappear completely:
# Silent error - exception disappears
Async do
http = Async::HTTP::Internet.new
Async do
response = http.get('https://invalid-url')
# Connection error raised here disappears
process(response)
end
end
Proper error handling catches exceptions at every async boundary:
# Correct error handling
Async do
http = Async::HTTP::Internet.new
Async do
begin
response = http.get('https://invalid-url')
process(response)
rescue => e
log_error("Request failed: #{e.message}")
end
end
end
Resource leaks occur when async operations fail before releasing resources. A connection opened in a callback might never close if an exception interrupts execution:
# Resource leak risk
Async do
socket = TCPSocket.new('example.com', 80)
# Exception here leaks socket
data = process_request
socket.write(data)
socket.close
end
Always use ensure blocks to guarantee resource cleanup:
# Guaranteed cleanup
Async do
socket = TCPSocket.new('example.com', 80)
begin
data = process_request
socket.write(data)
ensure
socket.close
end
end
Callback ordering assumptions create race conditions. Operations complete in unpredictable order based on network timing, system load, and event loop scheduling:
# Race condition - order not guaranteed
results = []
Async do
[1, 2, 3].each do |i|
Async do
response = fetch_data(i)
results << response # Order undefined
end
end
end
Synchronize results explicitly when order matters:
# Guaranteed ordering
results = []
Async do |task|
tasks = [1, 2, 3].map do |i|
task.async { fetch_data(i) }
end
results = tasks.map(&:wait) # Results in order of tasks array
end
Timeout handling in async code requires explicit timeout mechanisms. Blocking operations timeout automatically after TCP timeout periods, but async operations might wait indefinitely without timeout handling:
# No timeout - might wait forever
Async do
response = http.get('https://slow-server.com')
process(response)
end
Implement timeouts at operation level:
# Explicit timeout handling
Async do
Async::Task.current.with_timeout(5) do
response = http.get('https://slow-server.com')
process(response)
end
rescue Async::TimeoutError
handle_timeout
end
Mixing blocking and non-blocking libraries causes unpredictable behavior. A gem performing blocking I/O within an async context blocks the event loop:
# Blocking gem ruins async performance
require 'async'
require 'net/http' # Blocking library
Async do
# This blocks the event loop
response = Net::HTTP.get(URI('https://example.com'))
process(response)
end
Use async-compatible libraries or isolate blocking operations:
# Async-compatible library
require 'async/http/internet'
Async do
internet = Async::HTTP::Internet.new
response = internet.get('https://example.com')
process(response.read)
end
Debugging async code reveals incomplete stack traces. Exceptions occur in callback context, not the original calling context:
# Debugging challenge
Async do
fetch_data.then do |data|
process(data)
# Exception here shows truncated stack trace
end
end
Add contextual logging and error tracking throughout async chains:
# Improved debugging
Async do
logger.debug("Starting fetch_data")
begin
fetch_data.then do |data|
logger.debug("Processing data: #{data.size} bytes")
process(data)
end
rescue => e
logger.error("Failed: #{e.message}")
logger.error(e.backtrace.join("\n"))
raise
end
end
Reference
Execution Model Comparison
| Characteristic | Blocking | Non-Blocking |
|---|---|---|
| Control Flow | Sequential, linear | Callback-based, promise chains |
| Thread Usage | One per connection | Fixed thread pool |
| Memory Overhead | 1-2MB per thread | Minimal per connection |
| Context Switching | Frequent, OS-managed | Minimal, within thread |
| Error Handling | Exceptions at call site | Callback error parameters |
| Debugging | Standard stack traces | Fragmented traces |
| Scalability | Hundreds of connections | Tens of thousands |
| Library Support | Universal | Requires async libraries |
| Development Complexity | Low | High |
| Latency Profile | Consistent | Variable under load |
Ruby Async Libraries
| Library | Approach | Concurrency Model | Status |
|---|---|---|---|
| async | Fiber-based event loop | Single-threaded async | Active |
| EventMachine | Reactor pattern | Single-threaded callbacks | Maintenance |
| Celluloid | Actor model | Multi-threaded actors | Discontinued |
| Concurrent Ruby | Thread abstractions | Multi-threaded futures | Active |
| nio4r | Low-level I/O multiplexing | Platform-specific selectors | Active |
System Calls for Non-Blocking I/O
| System Call | Platform | Max Connections | Performance |
|---|---|---|---|
| select | POSIX | 1024 typical | O(n) scan |
| poll | POSIX | Unlimited | O(n) scan |
| epoll | Linux | Unlimited | O(1) events |
| kqueue | BSD/macOS | Unlimited | O(1) events |
| IOCP | Windows | Unlimited | O(1) completion |
Async Pattern Methods
| Pattern | Implementation | Use Case |
|---|---|---|
| Callbacks | Function passed to async operation | Simple single operation |
| Promises | Object representing future value | Chaining operations |
| Futures | Placeholder for result | Parallel operations |
| Async/Await | Syntax sugar for promises | Sequential-looking async code |
| Reactive Streams | Observable event sequences | Continuous data streams |
| Actor Model | Message-passing concurrency | Stateful concurrent components |
Performance Characteristics
| Metric | Blocking | Non-Blocking |
|---|---|---|
| Throughput | thread_count × request_rate | event_loop_efficiency × callback_rate |
| Memory per 1000 connections | 1-2GB | 10-50MB |
| Context switches per request | 2-4 | 0-1 |
| CPU efficiency | 60-70% | 85-95% |
| Latency P50 | 10-20ms | 5-10ms |
| Latency P99 | 20-40ms | 50-200ms |
Ruby Fiber API
| Method | Purpose | Behavior |
|---|---|---|
| Fiber.new | Create fiber | Returns new fiber object |
| Fiber.yield | Suspend fiber | Returns control to resumer |
| fiber.resume | Resume fiber | Continues execution |
| Fiber.current | Get current fiber | Returns executing fiber |
| fiber.alive? | Check fiber state | Returns true if not terminated |
| fiber.transfer | Transfer to fiber | Resumes target, suspends current |
Common Error Patterns
| Error Type | Blocking Behavior | Non-Blocking Behavior |
|---|---|---|
| Connection timeout | Raises exception after timeout | Callback with timeout error |
| Network error | Raises exception immediately | Callback with error parameter |
| Resource exhaustion | Blocks waiting for resources | Queues operation or rejects |
| Uncaught exception | Propagates to caller | May terminate event loop |
| Deadlock | Hangs indefinitely | Detectable through timeouts |
Async HTTP Client Examples
| Operation | Async Implementation |
|---|---|
| GET request | internet.get(url) |
| POST with body | internet.post(url, headers, body) |
| Concurrent requests | tasks.map async { internet.get(url) } |
| Timeout handling | task.with_timeout(seconds) { operation } |
| Error handling | rescue StandardError in async block |
| Connection pooling | internet reuses connections automatically |
Event Loop Tuning
| Parameter | Description | Typical Value |
|---|---|---|
| Max concurrent operations | Concurrent async operations | 1000-10000 |
| Selector timeout | Wait time for I/O events | 1-100ms |
| Callback execution limit | Max time per callback | 1-10ms |
| Queue depth | Pending operations buffer | 1000-100000 |
| Worker threads | Thread pool for blocking ops | CPU cores × 2 |