4. 原子操作实现计数器

任务说明

实现一个多线程环境下的安全计数器。多个线程同时对同一个计数器执行递增操作,要求最终结果与线程数量、每个线程递增次数严格一致,且全程无数据竞争。使用 AtomicUsize 配合 std::thread 完成,不借助 Mutex 或 RwLock。

核心实现

use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;

fn main() {
    // 目标:8个线程,每个线程递增10000次
    const THREADS: usize = 8;
    const INCREMENTS_PER_THREAD: usize = 10_000;

    // 原子计数器,初始值为0
    let counter = AtomicUsize::new(0);

    // 创建线程句柄容器
    let mut handles = Vec::with_capacity(THREADS);

    for _ in 0..THREADS {
        // 每个线程持有一个 AtomicUsize 的引用
        let counter_ref = &counter;
        handles.push(thread::spawn(move || {
            for _ in 0..INCREMENTS_PER_THREAD {
                // Relaxed 即可满足计数需求(无跨线程顺序约束)
                counter_ref.fetch_add(1, Ordering::Relaxed);
            }
        }));
    }

    // 等待所有线程完成
    for handle in handles {
        handle.join().expect("线程执行失败");
    }

    // 读取最终值
    let final_value = counter.load(Ordering::Relaxed);
    let expected = THREADS * INCREMENTS_PER_THREAD;

    println!("最终计数: {}", final_value);
    println!("期望值:   {}", expected);
    assert_eq!(final_value, expected, "计数结果与期望不符,存在数据竞争");
    println!("验证通过:无数据竞争,计数准确");
}

关键设计说明

  • 原子类型:AtomicUsize 保证 fetch_add 的读-改-写操作在硬件层面不可分割,无需加锁。
  • 内存序:此处仅需计数准确,无跨线程数据依赖,使用 Ordering::Relaxed 即可,开销最低。
  • 所有权与借用:通过 &counter 将原子变量的引用移入闭包,多个线程共享同一计数器。
  • 线程同步:join() 确保所有线程结束后再读取最终值,避免读取过早。

编译与运行

# 创建项目(若已有 Cargo.toml 可跳过)
cargo new atomic_counter
cd atomic_counter

# 将上述代码写入 src/main.rs

# 编译并运行
cargo run

验证方法与判定标准

程序运行后,控制台输出两行数值。判定通过需同时满足:

  1. 输出行 最终计数: 80000 与 期望值: 80000 数值完全一致;
  2. 程序正常退出,无 panic,且打印 验证通过:无数据竞争,计数准确。

若改用普通 usize 而非原子类型,多次运行大概率出现计数小于 80000 的情况,可自行替换对比验证原子操作的必要性。

预期输出

最终计数: 80000
期望值:   80000
验证通过:无数据竞争,计数准确
AtomicUsize 多线程递增时序 线程1 线程2 线程3 … 线程8 fetch_add(1, Relaxed) — 硬件级原子读-改-写,无锁 计数器 0→ 80000 fetch_add 操作点 共享 AtomicUsize