同步
同步
当多个线程共享数据时,必须同步读写访问以避免数据损坏。
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:
Sync 与 Send(见接口)。Sync 表示“引用可在线程间共享”,
Send 表示“值可跨线程转移”。更多内容见 Rust Book 的
“Fearless Concurrency”。