Skip to content

Instantly share code, notes, and snippets.

@cassc
Last active June 3, 2024 03:02
Show Gist options
  • Save cassc/e4189d5346612e678f9503a983d3f03a to your computer and use it in GitHub Desktop.
Save cassc/e4189d5346612e678f9503a983d3f03a to your computer and use it in GitHub Desktop.
tokio blocking io

tokio vs threadpool

Tokio is an asynchronous runtime for the Rust programming language, and it is more than just thread pools. Here's a brief overview of how Tokio is implemented:

  1. Event Loop: Tokio uses an event-driven architecture, leveraging an event loop to manage tasks and I/O events. The core of Tokio's runtime is the mio library, which provides a low-level event loop backed by OS-specific mechanisms like epoll (Linux), kqueue (BSD), and IOCP (Windows).

  2. Futures and Tasks: Tokio's concurrency model is based on Rust's Future trait. Tasks are units of work that implement the Future trait, and they are polled by the runtime to make progress. When a task is not ready to make progress (e.g., waiting for I/O), it yields control back to the runtime.

  3. Thread Pool: Tokio uses a work-stealing thread pool to execute tasks. This means that multiple worker threads are available, and they can steal tasks from each other to balance the load. The thread pool dynamically adjusts the number of threads based on the workload.

  4. Task Scheduling: Tokio employs a cooperative scheduling approach for tasks. When a task is ready to run, it is scheduled on the runtime's executor. Tasks are generally non-blocking and yield control back to the executor when they can't make progress. This helps prevent any single task from monopolizing the CPU.

  5. I/O Resources: Tokio provides asynchronous versions of standard I/O primitives (e.g., TCP/UDP sockets, file I/O). These primitives are integrated with the event loop, allowing the runtime to efficiently manage I/O operations without blocking threads.

  6. Synchronization Primitives: Tokio offers a set of asynchronous synchronization primitives, such as mutexes, condition variables, and channels. These primitives are designed to work seamlessly with the async/await syntax and the non-blocking nature of Tokio.

In summary, while thread pools are a part of Tokio's implementation, the runtime is built around a sophisticated event loop and task scheduling system that efficiently manages asynchronous operations and I/O events. This design allows Tokio to provide high concurrency and performance for asynchronous applications in Rust.

blocking io in tokio

In Tokio, you should avoid blocking operations inside asynchronous functions because they can block the entire thread and prevent other tasks from making progress. Here’s how to handle blocking operations like file reading or network requests properly within Tokio:

File Reading

For file I/O, you can use asynchronous file operations provided by the tokio::fs module instead of standard blocking file I/O operations.

Example:

use tokio::fs::File;
use tokio::io::{self, AsyncReadExt};

async fn read_file_async(path: &str) -> io::Result<Vec<u8>> {
    let mut file = File::open(path).await?;
    let mut contents = vec![];
    file.read_to_end(&mut contents).await?;
    Ok(contents)
}

Network Requests

For network requests, you should use asynchronous networking libraries such as reqwest for HTTP requests, which is built to work with Tokio's async runtime.

Example:

use reqwest;

async fn fetch_url_async(url: &str) -> Result<String, reqwest::Error> {
    let response = reqwest::get(url).await?;
    let body = response.text().await?;
    Ok(body)
}

Blocking Operations

If you must perform a blocking operation, you should offload it to a dedicated blocking thread using Tokio's spawn_blocking function. This function runs the blocking code in a separate thread pool, preventing it from blocking the async runtime.

Example:

use tokio::task;

async fn blocking_task() -> std::io::Result<()> {
    let result = task::spawn_blocking(|| {
        // Perform the blocking operation here
        std::fs::read_to_string("some_file.txt")
    })
    .await??;

    println!("File contents: {}", result);
    Ok(())
}

Summary

  • Avoid blocking operations inside async functions directly.
  • Use async file and network I/O provided by Tokio and other compatible libraries.
  • Offload blocking tasks using tokio::task::spawn_blocking.

This approach ensures that your async functions remain non-blocking, allowing the Tokio runtime to schedule other tasks efficiently.

concurrency vs parallism in tokio

Concurrency and parallelism are not the same thing. If you alternate between two tasks, then you are working on both tasks concurrently, but not in parallel. For it to qualify as parallel, you would need two people, one dedicated to each task.

One of the advantages of using Tokio is that asynchronous code allows you to work on many tasks concurrently, without having to work on them in parallel using ordinary threads. In fact, Tokio can run many tasks concurrently on a single thread!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment