cratosw

同步

同步

当多个线程共享数据时,必须同步读写访问以避免数据损坏。
C# 常用 lock 关键字(底层等价为对 .NET Monitor 的异常安全封装):

using System;
using System.Threading;

var dataLock = new object();
var data = 0;
var threads = new List<Thread>();

for (var i = 0; i < 10; i++)
{
    var thread = new Thread(() =>
    {
        for (var j = 0; j < 1000; j++)
        {
            lock (dataLock)
                data++;
        }
    });
    threads.Add(thread);
    thread.Start();
}

foreach (var thread in threads)
    thread.Join();

Console.WriteLine(data);

Rust 中需要显式使用并发原语(例如 Mutex):

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

fn main() {
    let data = Arc::new(Mutex::new(0)); // (1)

    let mut threads = vec![];
    for _ in 0..10 {
        let data = Arc::clone(&data); // (2)
        let thread = thread::spawn(move || { // (3)
            for _ in 0..1000 {
                let mut data = data.lock().unwrap();
                *data += 1; // (4)
            }
        });
        threads.push(thread);
    }

    for thread in threads {
        thread.join().unwrap();
    }

    println!("{}", data.lock().unwrap());
}

要点:

  • 多线程共享 Mutex 所有权时,需要 Arc 包一层(1)。Arc 是原子引用计数: 每次克隆(2)计数 +1,每次释放计数 -1,降到 0 时释放其持有的数据。 详见内存管理
  • 每个线程闭包都会获得其“克隆后的共享引用”所有权(3)。
  • *data += 1(4)虽然长得像指针写入,但这是在 mutex guard 保护下更新数据。

与 C# 示例不同:C# 里把 lock 去掉后程序仍可编译但变成线程不安全;Rust 里若修改为 线程不安全写法,编译器通常会直接拒绝编译。
这体现了两种模型的差异:C#/.NET 更依赖开发者正确使用同步原语,而 Rust 会把更多并发安全 约束前置到类型系统与编译期。

编译器之所以能做到这一点,是因为 Rust 类型系统里有并发相关标记 trait: SyncSend(见接口)。Sync 表示“引用可在线程间共享”, Send 表示“值可跨线程转移”。更多内容见 Rust Book 的 “Fearless Concurrency”。

On this page