Wang's blog

十、标准库 - 并发

Published on

安全并高效地处理并发是Rust的主要目标之一。Rust使用所有权与类型系统可以同时处理内存安全与并发安全问题。

线程(Thread)

// 使用spawn建立线程,使其运行一个闭包
let handle = thread::spawn(|| {
    for i in 1..10 {
        println!("hi number {} from the spawned thread!", i);
    }
});

// 使用join等待线程结束
handle.join().unwrap();

// 使用sleep令当前线程休眠
thread::sleep(Duration::from_millis(1));

// 为了使线程闭包能够使用外部变量,需要使用move关键字移动所有权
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
    println!("Here's a vector: {:?}", v);
});

通道

通过消息传递来进行安全的并发是一种越来越流行的思想。因此Rust标准库中实现了通道,它可以由sender发送数据给receiver。

// 建立通道,获得sender与receiver
let (tx, rx) = mpsc::channel();

// 在新线程中使用sender发送数据,注意需要将sender移动至新线程
thread::spawn(move || {
    let val = String::from("Hi");
    // 为了保证安全,使用通道发送数据时,数据被移动
    tx.send(val).unwrap();
});

// 在主线程中使用receiver接收数据
let received = rx.recv().unwrap();
println!("Got: {}", received);

通道可以有多个sender,但只能有一个receiver

let (tx, rx) = mpsc::channel();

// 通过复制可以有多个sender
let tx1 = tx.clone();
thread::spawn(move || {
    let vals = vec![
        String::from("hi"),
        String::from("from"),
        String::from("the"),
        String::from("thread"),
    ];

    for val in vals {
        tx1.send(val).unwrap();
        thread::sleep(Duration::from_secs(1));
    }
});

// 通过复制可以有多个sender
thread::spawn(move || {
    let vals = vec![
        String::from("more"),
        String::from("messages"),
        String::from("for"),
        String::from("you"),
    ];

    for val in vals {
        tx.send(val).unwrap();
        thread::sleep(Duration::from_secs(1));
    }
});

// 只能有一个receiver,可以使用for语句迭代
for received in rx {
    println!("Got: {}", received);
}

互斥锁(Mutex)

使用通道时,不同线程并不共享数据,另外一种并发方式是多个线程共享数据。但是为了保证同一时间只能有一个线程可以访问数据,需要使用互斥锁。

// 声明一个互斥锁Mutex,为了可以由多个线程共享,需要声明为线程安全智能指针Arc
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

// 启动10个线程
for _ in 0..10 {
    // 复制Mutex指针
    let counter = Arc::clone(&counter);
    // 需要使用move移动指针所有权
    let handle = thread::spawn(move || {
        // 使用数据前需要使用lock加锁
        let mut num = counter.lock().unwrap();
        // 之后可以使用数据
        *num += 1;
        // 函数退出后自动解锁
    });
    handles.push(handle);
}

// 等待所有线程结束
for handle in handles {
    handle.join().unwrap();
}

Sync与Send特性

Rust语言内置了Sync与Send两个标记特性用于处理并发。它们用于标记,因此没有方法需要实现。但是要实现它们的功能需要使用unsafe代码。

  • Send:实现此特性的类型,其值的所有权可以在线程间转移
  • Sync:实现此特性的类型,其值可以由多个线程引用