rust从入门到放弃(十二):多线程

和其他语言一样rust 也支持多线程,不过目前感觉使用多线程的最爽的还是 go ,因为你只需要执行 go 就可以启动一个协程。回到正题,我们说一下rust 的多线程。

rust从入门到放弃(十二):多线程

rust创建线程,通过 spawn 方法创建线程,然后里面放入闭包,如下:

thread::spawn(move || {
      // 线程逻辑
});

我们可以编写一个最简单的demo

fn main() {
    let child = thread::spawn(move || {
        println!("child");
    });
    println!("parent");
    child.join();
}

上面的程序会输出

parent
child

其中 child 是由子线程输出的。

多线程总是有个避不开的问题,并发冲突,多个线程同时修改某个变量或者执行某段代码(临界区)的的时候就会出现冲突,这里冲突本质上是由于多CPU多核配合多级缓存导致的。我们来看下面这个例子,我们尝试对变量进行乘 2 操作。

fn main() {
    let mut health = 12;
    thread::spawn( || {
        health *= 2;
    });
    println!("{}", health);
}

如果看过之前介绍闭包的文章,就会发现,这里的闭包采用的可变借用的方法捕获,这就需要保证,health 的生命周期要大于闭包,但这个闭包又是在另外一个线程中执行,编译器在编译代码的时候无法保证,所以会报错,在之前闭包的文章中,我们通过move 关键字把health的所有权转移到闭包里面。

fn main() {
    let mut health = 12;
    thread::spawn( move|| {
        health *= 2;
    });
    println!("{}", health);
}

这时候代码虽然编译成功了,但是 health 的值没有修改。这个其实非常容易理解,因为这里使用了 move 相当于变量赋值, health 是基础类型,这里赋值是拷贝数据,闭包里面的health 和外部的health 没有关系,所以并不会影响外部的health ,这里聪明的童鞋又会想了,如果我传入一个 vec 复杂类型呢? 那么这里还有问题,当所有权move 专业到闭包里面了,外部的heatlh 将失效,最后一行打印将导致编译错误。什么鬼,难道rust 不支持多线程之间共享数据?

这当然不可能,但rust 只是阻止了线程之间不安全的共享。我们仍然可以通过锁机制在线程间完成共享,如下面代码启动了两个线程,分别对数据进行 +100 和 -100 操作。这里关键是第一行里面 Arc 和Mutex ,其中 Mutex 是锁,保证多线程互斥,Arc 是引用计数,因为我们不能直接把 mutex 直接 move 到两个线程里面,这样就冲突了,所以我们通过clone 将引用计数 +1 (不是拷贝),然后分别传到两个线程里面。这样两个线程就可以安全的使用同一个锁了。

const COUNT: u32 = 100;
fn main() {
    let global = Arc::new(Mutex::new(0));
    let clone1 = global.clone(); //引用计数 +1
    let thread1 = thread::spawn(move|| {
        for _ in 0..COUNT {
            let mut value = clone1.lock().unwrap();//获取锁
            *value += 1;
        } });
    let clone2 = global.clone(); //引用计数 +1
    let thread2 = thread::spawn(move|| {
        for _ in 0..COUNT {
            let mut value = clone2.lock().unwrap();//获取锁
            *value -= 1;
        } });
    thread1.join().ok();
    thread2.join().ok();
    println!("final value: {:?}", global);
}

程序安全并发,最终输出为 0 。这里并没有单独释放锁,因为每次for 循环执行完,value 生命周期结束,锁自然就释放了。整个模型如下所示:

rust从入门到放弃(十二):多线程

最终的数据被 Mutex 保护起来,然后多个线程通过 Arc 引用共享这个锁。除了上面的互斥锁,rust 也支持 RwLock 读写锁 和 Atomic 原子操作。其中,读写锁使用方式如下

let global = Arc::new(RwLock::new(0));

let mut value = clone1.write().unwrap();
let mut value = clone1.read().unwrap();

原子操作使用方式如下:

let global = Arc::new(AtomicIsize::new(0));

clone1.fetch_add(1, Ordering::SeqCst);  // 加法
clone2.fetch_sub(1, Ordering::SeqCst);  // 减法

那么是不是任何类型都可以通过上面的move 传递到线程里面呢,当然也不是,这里就涉及到rust 里面Send 和Sync 这两个trait ,关于Send 和 Sync 相关内容等后面文章单独介绍。

展开阅读全文

页面更新:2024-04-16

标签:多核   个协   赋值   线程   原子   变量   所有权   生命周期   入门   冲突   两个   类型   代码   操作   数据   文章   科技

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top