Rust学习第十四天——多线程同时运行代码

并发

  • Concurrent:程序的不同部分之间独立的执行

  • Parallel:程序的不同部分同时运行

  • Rust无畏并发:允许你编写没有细微的Bug的代码,并在不引入新bug的情况下易于重构

使用线程同时运行代码

进程与线程

  • 在大部分OS里,代码运行在进程(process)中,OS同时管理多个进程

  • 在你的程序里,各独立部分可以同时运行,运行这些独立部分的就是线程

  • 多线程运行:

  • 提升性能表现

  • 增加复杂性:无法保障各线程的执行顺序

多线程可导致的问题

  • 竞争状态,线程以不一致的顺序访问数据或资源

  • 死锁,两个线程彼此等待对方使用完所持有的资源,线程无法继续

  • 只在某些情况下发生Bug,很难可靠的复制现象和修复

实现线程的方式

  • 通过调用OS的API来创建线程:1:1模型

  • 需要较小的运行时

  • 语言自己实现的线程(绿色线程):M:N模型

  • 需要更大的运行时

  • Rust:需要先权衡运行时的支持

  • Rust标准库仅提供1:1模型的线程

通过spawn创建新线程

  • 通过thread::spawn函数可以创建新线程:

  • 参数:一个闭包(在新线程里运行的代码)

use std::{ thread, time::Duration };

fn main() {
    thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
    
    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}

通过join Handle来等待所有线程的完成

  • thread::spawn函数的返回所有值类型时JoinHandle

  • JoinHandle持有值得所有权

  • 调用join方法,可以等待对应的其他线程的完成

  • join方法:调用handle的join方法会阻止当前运行线程的执行,直到handle所表示的这些线程终结

加入:

use std::{ thread, time::Duration };

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
    
    handle.join().unwrap();
}

使用Move闭包

  • move闭包通常和thread::spawn函数一起使用,它允许你使用其他线程的数据

  • 创建线程时,把值的所有权从一个线程转移到另一个线程

use std::{ thread, time::Duration };

fn main() {
    let v = vec![1, 2, 3];
    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });

    handle.join().unwrap();
}

使用消息传递来跨线程传递数据

消息传递

  • 一种很流行且能保证安全并发的技术就是:消息传递

  • 线程(或Actor)通过彼此发送消息(数据)来进行通信

  • Go语言的名言:不要用共享内存来通信,要用通信来共享内存

  • Rust:Channel(标准库提供)

Channel

  • Channel包含:发送端、接收端

  • 调用发送端的方法,发送数据

  • 接收端会检查和接收到达的数据

  • 如果发送端、接收端中任意一段被丢弃,那么Channel就“关闭”了

创建Channel

  • 使用mpsc::channel函数来创建Channel

  • mpsc来表示multiple producer、single consumer

  • 返回一个tuple(元组):里面分别是发送端、接收端

  • (例子)

use std::{ thread, sync::mpsc };

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
    });

    let received = rx.recv().unwrap();
    println!("Cot: {}", received);
}

发送端的send方法

  • 参数:想要发送的数据

  • 返回: Result<T, E>

  • 如果有问题(例如接收端已经被丢弃),就返回一个错误

接收端的方法

  • recv方法:阻止当前线程的执行,直到Channel中有值被送来

  • 一旦有值收到,就返回Result<T, E>

  • 当发送端关闭,就会收到一个错误

  • try_recv方法:不会阻塞,

  • 立即返回Result<T, E>:

  • 有数据达到:返回OK,里面包含着数据

  • 否则,返回错误

  • 通常会使用循环调用来检查try_recv的结果

Channel和所有权转移

  • 所有权在消息传递中非常重要:能帮你编写安全、并发的代码

发送多个值,看到接受者在等待

use std::{ thread, sync::mpsc, time::Duration };

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let vals = vec![
            String::from("hi"),
            String::from("from"),
            String::from("the"),
            String::from("thread"),
        ];

        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_millis(1));
        }
    });

    for received in rx {
        println!("Cot: {}", received);
    }
}

通过克隆创建多个发送者

use std::{ thread, sync::mpsc, time::Duration };

fn main() {
    let (tx, rx) = mpsc::channel();

    let tx1 = mpsc::Sender::clone(&tx);
    thread::spawn(move || {
        let vals = vec![
            String::from("1: hi"),
            String::from("1: from"),
            String::from("1: the"),
            String::from("1: thread"),
        ];

        for val in vals {
            tx1.send(val).unwrap();
            thread::sleep(Duration::from_millis(200));
        }
    });

    thread::spawn(move || {
        let vals = vec![
            String::from("hi"),
            String::from("from"),
            String::from("the"),
            String::from("thread"),
        ];

        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_millis(200));
        }
    });

    for received in rx {
        println!("Cot: {}", received);
    }
}

共享状态的并发

使用共享来实现并发

使用Mutex来每次只允许一个线程来访问数据

  • Mutex时mutual exclusion(互斥锁)的简写

  • 在同一时刻,Mutex只允许一个线程来访问某些数据

  • 想要访问数据:

  • 线程必须首先获取互斥锁(lock)

  • lock数据结构是mutex的一部分,它能跟踪谁对数据拥有独占访问权

  • Mutex通常被描述为:通过锁定系统来保护它所持有的的数据

Mutex的两条规则

Mutex<T>的API

use std::{ sync::Mutex };

fn main() {
    let m = Mutex::new(5);

    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }

    println!("m = {:?}", m);
}

多线程共享Mutex<T>

例子报错

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

fn main() {
    let counter = Mutex::new(0);
    let mut handles = vec![];
    
    for _ in 0..10 {
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            
            *num +=1;
        });
        handles.push(handle);
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("Result: {}", *counter.lock().unwrap());
}

多线程的多重所有权

使用Arc<T>来进行原子引用计数

  • Arc<T>和Rc<T>类似,它可以用于并发情景

  • A:atomic,原子的

  • 需要牺牲性能作为代价

  • Arc<T>和Rc<T>的API相同

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

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

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();

            *num +=1;
        });
        handles.push(handle);
    }

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

    println!("Result: {}", *counter.lock().unwrap());
}

通过Send和Sync Trait来拓展并发

Send和Sync trait

Send:允许线程间转移所有权

Sync:允许多线程访问

手动实现Send和Sync是不安全的

总结

感觉学的东西越来越难了,不过我相信,坚持下去,总有顺手的一天。

三毛在散文《简单》里写道:“我避开无事时过分热络的友谊,这使我少些负担和承担。我不多说无谓的闲言,这使我觉得清畅。我当心的去爱别人,因为比较不会泛滥。我不求深刻,只求简单。”
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值