温馨提示×

CentOS上Rust的并发编程实践

小樊
42
2025-09-28 10:36:17
栏目: 编程语言

Installing Rust on CentOS
To start concurrent programming in Rust on CentOS, first install Rust using rustup, the official Rust toolchain installer. Run the following command in your terminal:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

After installation, reload your shell environment to add Rust to your PATH:

source $HOME/.cargo/env

Verify the installation with rustc --version and cargo --version.

Creating a New Rust Project
Use Cargo (Rust’s package manager and build tool) to create a new project for concurrent programming:

cargo new concurrency_demo
cd concurrency_demo

This generates a basic project structure with a src/main.rs file and a Cargo.toml manifest.

1. Thread-Based Concurrency
Rust’s standard library provides the std::thread module for creating and managing threads. The thread::spawn function creates a new thread that executes the provided closure. Use join() to wait for the thread to finish.
Example:

use std::thread;

fn main() {
    let handle = thread::spawn(|| {
        println!("Hello from a spawned thread!");
    });
    println!("Hello from the main thread!");
    handle.join().unwrap(); // Blocks until the thread completes
}

This demonstrates basic thread creation and synchronization.

2. Message Passing with Channels
Rust encourages message passing over shared state to avoid data races. The std::sync::mpsc (Multiple Producer, Single Consumer) module provides channels for thread-safe communication.
Example:

use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel(); // Create a channel (tx: transmitter, rx: receiver)
    thread::spawn(move || {
        let val = String::from("Message from thread");
        tx.send(val).unwrap(); // Send data to the main thread
    });
    let received = rx.recv().unwrap(); // Receive data (blocks until a message arrives)
    println!("Received: {}", received);
}

Channels ensure safe communication between threads without explicit locking.

3. Shared State with Arc and Mutex
For cases where shared state is unavoidable, use Arc (Atomic Reference Counting) for thread-safe reference counting and Mutex (Mutual Exclusion) to protect data from concurrent access.
Example:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0)); // Wrap the counter in Arc and Mutex
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter); // Clone the Arc for each thread
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap(); // Acquire the mutex lock
            *num += 1; // Modify the shared data
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap(); // Wait for all threads to finish
    }

    println!("Final counter value: {}", *counter.lock().unwrap());
}

Arc ensures the counter is safely shared across threads, while Mutex prevents simultaneous modifications.

4. Asynchronous Programming with Tokio
For high-performance I/O-bound tasks (e.g., network servers), use Rust’s async/await syntax with an asynchronous runtime like tokio. Add tokio to your Cargo.toml:

[dependencies]
tokio = { version = "1", features = ["full"] }

Example: A simple TCP echo server that spawns a new task for each client connection:

use tokio::net::TcpListener;
use tokio::prelude::*;

#[tokio::main] // Macro to set up the Tokio runtime
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let listener = TcpListener::bind("127.0.0.1:8080").await?; // Bind to localhost:8080
    println!("Server listening on port 8080");

    loop {
        let (mut socket, addr) = listener.accept().await?; // Accept a new connection
        println!("New connection from {:?}", addr);

        // Spawn a new async task to handle the client
        tokio::spawn(async move {
            let mut buf = [0; 1024]; // Buffer for reading data

            loop {
                match socket.read(&mut buf).await { // Read data from the socket
                    Ok(n) if n == 0 => return, // Connection closed by client
                    Ok(n) => {
                        if socket.write_all(&buf[0..n]).await.is_err() { // Echo data back
                            eprintln!("Failed to write to socket");
                            return;
                        }
                    }
                    Err(e) => {
                        eprintln!("Failed to read from socket: {:?}", e);
                        return;
                    }
                }
            }
        });
    }
}

This example uses tokio::spawn to handle each client connection concurrently, enabling efficient handling of multiple clients.

Key Notes for Concurrent Programming in Rust

  • Ownership and Borrowing: Rust’s ownership model prevents data races at compile time. For example, you cannot mutate data while it’s borrowed mutably elsewhere.
  • Thread Safety: Use Arc for shared ownership and Mutex/RwLock for synchronized access to shared data. Avoid raw pointers or unsafe blocks unless absolutely necessary.
  • Async Best Practices: Use tokio::spawn to parallelize I/O-bound tasks, but avoid blocking operations (e.g., thread::sleep) in async tasks—use tokio::time::sleep instead.

By leveraging these tools and following Rust’s safety guarantees, you can build efficient and reliable concurrent applications on CentOS.

0