mirror of https://github.com/sunface/rust-course
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3.3 KiB
3.3 KiB
线程同步:并发原语和共享内存(下)
原子类型atomic
Mutex
用起来简单,但是无法并发读,RwLock
可以并发读,但是复杂且性能不够,那么有没有一种全能性选手呢? 欢迎我们的Atomic
闪亮登场。
从Rust1.34版本后,就正式支持原子类型。原子指的是一系列不可被CPU上下文交换的机器指令,这些指令组合在一起就形成了原子操作。在多核CPU下,都某个CPU核心开始运行原子操作时,会先暂停其它CPU内核对内存的操作,以保证原子操作不会被其它CPU内核所干扰。
由于原子操作是通过指令提供的支持,因此它的性能相比锁和消息传递会好很多。相比较于锁而言,原子类型不需要开发者处理加锁和释放锁的问题,同时支持修改,读取等操作,还具备较高的并发性能,几乎所有的语言都支持原子类型。
可以看出原子类型是无锁类型,但是无锁不代表无需等待,因为原子类型内部使用了CAS
循环,当大量的冲突发生时,该等待还是得等待!但是总归比锁要好。
使用原子类型作为全局变量
原子类型的一个常用场景,就是作为全局变量来使用:
use std::thread::{self, JoinHandle};
use std::sync::atomic::{Ordering, AtomicU64};
const N_TIMES: u64 = 100000;
const N_THREADS: usize = 10;
static R: AtomicU64 = AtomicU64::new(0);
fn reset() {
R.store(0, Ordering::Relaxed);
}
fn add_n_times(n: u64) -> JoinHandle<()> {
thread::spawn(move || {
for _ in 0..n {
R.fetch_add(1, Ordering::Relaxed);
}
})
}
fn main() {
loop {
reset();
let mut threads = Vec::with_capacity(N_THREADS);
for _ in 0..N_THREADS {
threads.push(add_n_times(N_TIMES));
}
for thread in threads {
thread.join().unwrap();
}
assert_eq!(N_TIMES * N_THREADS as u64, R.load(Ordering::Relaxed));
}
}
以上代码启动了数个线程,每个线程都在疯狂对全局变量进行加1操作, 最后将它与线程数 * 加1操作数进行比较,如果发生了因为多个线程同时修改导致了脏数据,那么这两个必将不相等。好在,它没有让我们失望,不仅快速的完成了任务,而且保证了100%的并发安全性。
在多线程环境中要使用Atomic
需要配合Arc
:
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{hint, thread};
fn main() {
let spinlock = Arc::new(AtomicUsize::new(1));
let spinlock_clone = Arc::clone(&spinlock);
let thread = thread::spawn(move|| {
spinlock_clone.store(0, Ordering::SeqCst);
});
// 等待其它线程释放锁
while spinlock.load(Ordering::SeqCst) != 0 {
hint::spin_loop();
}
if let Err(panic) = thread.join() {
println!("Thread had an error: {:?}", panic);
}
}
能替代锁吗
那么原子类型既然这么全能,它可以替代锁吗?答案是不行:
std::sync::atomic
包中仅提供了数值类型的原子操作:AtomicBool
,AtomicIsize
,AtomicUsize
,AtomicI8
,AtomicU16
等,而锁可以应用于各种类型- 在有些情况下,必须使用锁来配合,例如下面的
Condvar