侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

实现一个线程安全的计数器

2025-12-8 / 0 评论 / 4 阅读

题目

实现一个线程安全的计数器

信息

  • 类型:问答
  • 难度:⭐⭐

考点

并发编程,原子操作,互斥锁,性能考虑

快速回答

实现线程安全计数器的两种主要方法:

  • 原子操作:使用std::sync::atomic::AtomicUsize实现无锁计数器
  • 互斥锁:使用std::sync::Mutex保护共享的usize

关键选择依据:

  • 高并发场景优先选择原子操作(性能更优)
  • 需要复杂操作时使用互斥锁
## 解析

问题背景

在多线程环境中,多个线程同时修改共享计数器会导致数据竞争(Data Race)。Rust的所有权系统可以防止部分并发错误,但共享可变状态仍需显式同步。

解决方案

1. 原子操作实现(推荐)

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

struct AtomicCounter {
    count: AtomicUsize,
}

impl AtomicCounter {
    pub fn new() -> Self {
        AtomicCounter { count: AtomicUsize::new(0) }
    }

    pub fn increment(&self) {
        self.count.fetch_add(1, Ordering::SeqCst);
    }

    pub fn get(&self) -> usize {
        self.count.load(Ordering::SeqCst)
    }
}

fn main() {
    let counter = Arc::new(AtomicCounter::new());
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            for _ in 0..1000 {
                counter.increment();
            }
        }));
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Final count: {}", counter.get()); // 正确输出 10000
}

原理说明

  • AtomicUsize提供硬件级别的原子操作
  • fetch_add保证递增操作的原子性
  • Ordering::SeqCst确保全局内存顺序一致性

2. 互斥锁实现

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

struct MutexCounter {
    count: Mutex<usize>,
}

impl MutexCounter {
    pub fn new() -> Self {
        MutexCounter { count: Mutex::new(0) }
    }

    pub fn increment(&self) {
        let mut num = self.count.lock().unwrap();
        *num += 1;
    }

    pub fn get(&self) -> usize {
        *self.count.lock().unwrap()
    }
}

// 使用示例与原子版本相同

原理说明

  • Mutex通过锁机制保证独占访问
  • 获取锁时可能阻塞线程
  • 自动释放锁(通过Drop trait)

最佳实践

  • 优先选择原子操作:在简单计数器场景下,原子操作比互斥锁快5-10倍
  • 内存顺序选择
    • 大多数场景使用Ordering::Relaxed(仅需原子性)
    • 需要严格顺序时使用Ordering::SeqCst
  • 避免锁嵌套:互斥锁方案中不要在多处调用lock()(易死锁)

常见错误

  • 忘记同步:直接使用usize导致未定义行为
  • 锁中毒:线程panic时未处理PoisonError
  • 内存顺序错误:过度使用Ordering::SeqCst影响性能
  • 原子操作误用load/storefetch_add混用导致逻辑错误

性能对比

方法10线程×100k操作特点
原子操作~15ms无阻塞,CPU缓存友好
互斥锁~120ms线程阻塞,上下文切换开销

扩展知识

  • 无锁编程:原子操作是实现无锁数据结构的基础
  • 内存顺序
    • Relaxed:仅保证原子性
    • Release/Acquire:建立线程间happens-before关系
  • 替代方案
    • RwLock:适合读多写少场景
    • crossbeam:提供更高效的无锁数据结构