Rust多线程同步库spinning的使用,高性能自旋锁和同步原语实现并发控制

Rust多线程同步库spinning的使用,高性能自旋锁和同步原语实现并发控制

简介

spinning-rs是一个#![no_std]库,用于实现自旋锁(spinlocks),功能类似于spin库,但提供了增强特性如SIX(shared-intent-exclusive)读写锁,以及与lock_api的集成。

安装

在项目目录中运行以下Cargo命令:

cargo add spinning

或者在Cargo.toml中添加:

spinning = "0.1.0"

使用示例

基本自旋锁使用

use spinning::Spin;

fn main() {
    // 创建一个Spin保护的数据
    let data = Spin::new(0);
    
    // 获取锁并修改数据
    {
        let mut guard = data.lock();
        *guard += 1;
    } // 锁在这里自动释放
    
    // 读取数据
    let value = *data.lock();
    println!("Data value: {}", value); // 输出: Data value: 1
}

读写锁示例

use spinning::{RwSpin, RwSpinReadGuard, RwSpinWriteGuard};

fn main() {
    let data = RwSpin::new(0);
    
    // 多个读取者可以同时访问
    {
        let read_guard1: RwSpinReadGuard<_> = data.read();
        let read_guard2: RwSpinReadGuard<_> = data.read();
        println!("Readers see: {}, {}", *read_guard1, *read_guard2);
    }
    
    // 单个写入者独占访问
    {
        let mut write_guard: RwSpinWriteGuard<_> = data.write();
        *write_guard += 1;
    }
}

完整并发控制示例

use spinning::Spin;
use std::sync::Arc;
use std::thread;

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

    // 创建10个线程并发增加计数器
    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            for _ in 0..1000 {
                let mut num = counter.lock();
                *num += 1;
            }
        });
        handles.push(handle);
    }

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

    println!("Final counter value: {}", *counter.lock());
    // 正确情况下应该输出: Final counter value: 10000
}

完整示例demo

下面是一个使用spinning库实现生产者消费者模型的完整示例:

use spinning::{RwSpin, RwSpinReadGuard, RwSpinWriteGuard};
use std::sync::Arc;
use std::thread;
use std::time::Duration;

// 共享缓冲区结构
struct SharedBuffer<T> {
    data: RwSpin<Vec<T>>,
    capacity: usize,
}

impl<T> SharedBuffer<T> {
    fn new(capacity: usize) -> Self {
        SharedBuffer {
            data: RwSpin::new(Vec::with_capacity(capacity)),
            capacity,
        }
    }

    // 生产者写入数据
    fn produce(&self, item: T) {
        loop {
            {
                let mut buffer = self.data.write();
                if buffer.len() < self.capacity {
                    buffer.push(item);
                    return;
                }
            }
            thread::sleep(Duration::from_millis(10)); // 缓冲区满时短暂等待
        }
    }

    // 消费者读取数据
    fn consume(&self) -> Option<T> {
        let mut buffer = self.data.write();
        if !buffer.is_empty() {
            Some(buffer.remove(0))
        } else {
            None
        }
    }

    // 查看缓冲区大小
    fn size(&self) -> usize {
        let buffer = self.data.read();
        buffer.len()
    }
}

fn main() {
    let buffer = Arc::new(SharedBuffer::<i32>::new(10));
    let mut handles = vec![];

    // 创建3个生产者线程
    for i in 0..3 {
        let buffer = buffer.clone();
        handles.push(thread::spawn(move || {
            for j in 0..20 {
                let item = i * 100 + j;
                buffer.produce(item);
                println!("Producer {} produced: {}", i, item);
            }
        }));
    }

    // 创建2个消费者线程
    for _ in 0..2 {
        let buffer = buffer.clone();
        handles.push(thread::spawn(move || {
            for _ in 0..30 {
                if let Some(item) = buffer.consume() {
                    println!("Consumer got: {}", item);
                } else {
                    println!("Buffer empty, waiting...");
                }
                thread::sleep(Duration::from_millis(50));
            }
        }));
    }

    // 监控线程
    let buffer_clone = buffer.clone();
    handles.push(thread::spawn(move || {
        for _ in 0..10 {
            println!("Current buffer size: {}", buffer_clone.size());
            thread::sleep(Duration::from_millis(100));
        }
    }));

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

    println!("Final buffer size: {}", buffer.size());
}

特性

  1. #![no_std]兼容,适合嵌入式开发
  2. 提供自旋锁(Spin)和读写锁(RwSpin)实现
  3. 高性能的同步原语
  4. lock_api兼容的接口

许可证

spinning-rs采用双重许可:

  • Apache License 2.0
  • MIT license

可以根据需要选择任一许可证。


1 回复

Rust多线程同步库spinning的使用:高性能自旋锁和同步原语实现并发控制

spinning是Rust中一个轻量级的高性能同步原语库,它提供了基于自旋的锁和其他同步机制,特别适合在短期锁争用场景下提供更好的性能。

主要特性

  • 提供自旋锁(SpinLock)替代标准库的Mutex
  • 实现Once(一次性初始化)、Barrier(屏障)、RwLock(读写锁)等同步原语
  • 无系统调用开销(在锁争用时间短时性能更好)
  • 支持no_std环境

基本用法

首先在Cargo.toml中添加依赖:

[dependencies]
spinning = "0.1"

1. 自旋锁(SpinLock)使用示例

use spinning::SpinLock;
use std::sync::Arc;
use std::thread;

fn main() {
    // 创建一个Arc包装的自旋锁,保护一个整数
    let lock = Arc::new(SpinLock::new(0));
    
    let mut handles = vec![];
    
    // 创建10个线程并发访问
    for _ in 0..10 {
        let lock_clone = Arc::clone(&lock);
        let handle = thread::spawn(move || {
            // 获取锁并修改数据
            let mut data = lock_clone.lock();
            *data += 1;
        });
        handles.push(handle);
    }
    
    // 等待所有线程完成
    for handle in handles {
        handle.join().unwrap();
    }
    
    // 打印最终结果
    println!("Final value: {}", *lock.lock());
}

2. 一次性初始化(Once)使用示例

use spinning::Once;
use std::thread;

// 全局一次性初始化对象
static INIT: Once<()> = Once::new();

fn main() {
    let mut handles = vec![];
    
    // 创建5个线程尝试初始化
    for i in 0..5 {
        handles.push(thread::spawn(move || {
            // 只有一个线程会执行初始化代码
            INIT.call_once(|| {
                println!("Initializing from thread {}", i);
            });
        }));
    }
    
    // 等待所有线程完成
    for handle in handles {
        handle.join().unwrap();
    }
}

3. 读写锁(RwLock)使用示例

use spinning::RwLock;
use std::thread;

fn main() {
    // 创建一个读写锁保护的数据
    let lock = RwLock::new(5);
    
    // 多个读锁可以同时获取
    {
        let r1 = lock.read();  // 获取读锁
        let r2 = lock.read();  // 可以同时获取另一个读锁
        println!("Readers: {}, {}", *r1, *r2);
    }  // 读锁在这里自动释放
    
    // 使用线程作用域创建写线程
    thread::scope(|s| {
        // 第一个写线程
        s.spawn(|| {
            let mut w = lock.write();  // 获取写锁(独占)
            *w += 1;
            println!("Writer 1: {}", *w);
        });
        
        // 第二个写线程
        s.spawn(|| {
            let mut w = lock.write();  // 获取写锁(独占)
            *w += 1;
            println!("Writer 2: {}", *w);
        });
    });
}

性能考虑

自旋锁在以下场景表现良好:

  • 锁持有时间非常短
  • 线程数量不超过CPU核心数
  • 不需要长时间等待

但在以下情况可能不如系统锁:

  • 锁争用时间长(会导致CPU空转浪费资源)
  • 线程数远多于CPU核心数

高级用法

自定义自旋策略

use spinning::{SpinLock, SpinStrategy};

// 自定义自旋策略
struct MySpinStrategy;
impl SpinStrategy for MySpinStrategy {
    fn yield_now() {
        // 自定义自旋策略,例如增加一些延迟
        std::thread::yield_now();
        std::thread::sleep(std::time::Duration::from_nanos(100));
    }
}

fn main() {
    // 使用自定义策略创建自旋锁
    let lock = SpinLock::<i32, MySpinStrategy>::new(0);
    let mut data = lock.lock();
    *data = 42;
}

无堆分配(no_std)使用

#![no_std]

use spinning::SpinLock;

// 全局静态自旋锁
static GLOBAL_DATA: SpinLock<i32> = SpinLock::new(0);

fn main() {
    // 在no_std环境下使用自旋锁
    let mut data = GLOBAL_DATA.lock();
    *data += 1;
}

注意事项

  1. 自旋锁可能导致优先级反转问题,使用时需注意
  2. 长时间持有自旋锁会浪费CPU资源
  3. 在单核系统上使用自旋锁要特别小心,可能导致死锁

spinning库为Rust提供了轻量级、高性能的同步原语,特别适合在特定场景下替代标准库的同步机制以获得更好的性能。

回到顶部