< 返回版块

lsk569937453 发表于 2023-03-01 17:28

当前用的是Arc<Mutex>,场景主要用两个: 1.大量线程(20+)只读取变量,没有任何写入,性能要求较高。希望能不加锁读取,可以读取老的值。 2.少量线程(2-4)会对变量进行读写,无性能要求。

目前针对场景二是,直接加锁实现读写,应该没问题。 针对场景1的话,使用的代码老是出错:请问我该用Arc来做多线程读写吗?

评论区

写评论
lithbitren 2023-03-02 13:48

个人写的自旋锁性能很难超过Mutex

--
👇
Bai-Jinlin: 要不换成自旋锁试试?

Bai-Jinlin 2023-03-02 09:25

要不换成自旋锁试试?

github.com/shanliu/lsys 2023-03-02 09:10

如果值是i8应该不会有问题 但何必自己给自己挖坑 拆分拆分 搞几个原子锁代替不香么

👇
ThalliMega: 基本上肯定会触发data race 不知道会不会有其他的ub

--
👇
Pikachu: 这个真的不会触发什么UB吗?

--
👇
ThalliMega: 如果RwLock的性能不够,应该要考虑unsafe了?

struct Data;

static mut DATA: Data = Data;

fn update_data(new_data: Data) {
  unsafe { DATA = new_data }
}

lithbitren 2023-03-01 23:44

一种可供参考的轻量级读写锁的的写法,把数据和锁分离,读操作是无锁的,不过可靠性没有Rwlock高,理论上读还是有可能在高并发下出现非预期结果。

use std::sync::Mutex;
use std::thread;

#[derive(Default)]
struct Data {
    field: usize,
    write_lock: Mutex<()>
}

impl Data {
    fn read(&self) -> usize {
        // 如果写的时候不允许读的话,也可以判断后加锁,开销仍然比真正加锁低很多
        // if self.write_lock.is_poisoned() {
        //     let _ = self.write_lock.lock();
        // }
        self.field
    }

    fn write(&mut self, value: usize) {
        let _ = self.write_lock.lock();
        self.field = value;
    }
}

// 自定义struct不能直接static定义,至少需要包一个Option或其他智能指针
static mut DATA: Option<Data> = None;

fn main() {
    unsafe {
        DATA = Some(Data::default());
    }

    let read_thread = thread::spawn(move || {
        unsafe {
            let data = DATA.as_ref().unwrap();
            ...
            data.read();
            ...
        }
    });

    let write_thread = thread::spawn(move || {
        unsafe {
            let data = DATA.as_mut().unwrap();
            ...
            data.write(some_value);
            ...
        }
    });

    ...

}

要保证可靠性,要不然就用Rwlock,要不然就用第三方库arc_swap,arc-swap有点像原子操作,load方法下读性能很高,应该是lockfree的,不过store方法下的写操作性能一般,因为只能整体替换,无法类似写锁那样可变引用后局部修改。Arc<RwLock<_>>中其实主要性能消耗还是在RwLockArc本身对性能的消耗其实很小,而且其本身有一定特殊性,甚至在某些场景包上Arc比不包还要快。

ThalliMega 2023-03-01 23:36

基本上肯定会触发data race 不知道会不会有其他的ub

--
👇
Pikachu: 这个真的不会触发什么UB吗?

--
👇
ThalliMega: 如果RwLock的性能不够,应该要考虑unsafe了?

struct Data;

static mut DATA: Data = Data;

fn update_data(new_data: Data) {
  unsafe { DATA = new_data }
}

Pikachu 2023-03-01 22:47

这个真的不会触发什么UB吗?

--
👇
ThalliMega: 如果RwLock的性能不够,应该要考虑unsafe了?

struct Data;

static mut DATA: Data = Data;

fn update_data(new_data: Data) {
  unsafe { DATA = new_data }
}

github.com/shanliu/lsys 2023-03-01 21:46

core 欢迎你

--
👇
ThalliMega: 如果RwLock的性能不够,应该要考虑unsafe了?

struct Data;

static mut DATA: Data = Data;

fn update_data(new_data: Data) {
  unsafe { DATA = new_data }
}

ThalliMega 2023-03-01 21:00

如果RwLock的性能不够,应该要考虑unsafe了?

struct Data;

static mut DATA: Data = Data;

fn update_data(new_data: Data) {
  unsafe { DATA = new_data }
}

Pikachu 2023-03-01 19:53

我有一个非常暴力的办法:开20多个mpsc queue,每个consumer对应一个。queue的capacity设成1,如果出现overflow的话把最旧的数据丢弃掉。

每次consumer维护一个thread local。读取数据的时候,先尝试把queue里最新的读出来,同时更新thread local。如果queue是空的,那就直接返回thread local。

缺点是producer要往20多个channel里面写数据……但是既然你已经说了producer端的性能不重要,那应该可以接受?

(不太清楚这叫什么pattern,可能有个比较正式的名字吧?)

作者 lsk569937453 2023-03-01 19:20

是对场景1中有性能影响

--
👇
lsk569937453: 只用Mutex的话,每次读写数据也必须加lock,这样对我的场景2中有性能影响。能不能读数据的时候不加lock?

作者 lsk569937453 2023-03-01 19:19

只用Mutex的话,每次读写数据也必须加lock,这样对我的场景2中有性能影响。能不能读数据的时候不加lock?

Pikachu 2023-03-01 17:43

你这根本都不是Arc的问题。全局变量用不上Arc。

use std::sync::Mutex;
use std::thread::spawn;

static X: Mutex<i32> = Mutex::new(0);

fn main() {
    let handle1 = spawn(|| {
        X.lock().map(|mut xx| *xx += 1).unwrap();
    });

    let handle2 = spawn(|| {
        X.lock().map(|mut xx| *xx += 1).unwrap();
    });

    handle1.join().unwrap();
    handle2.join().unwrap();
}
Easonzero 2023-03-01 17:43

RwLock<T> 就行, 全局变量没必要包Arc

1 共 13 条评论, 1 页