八、并发线程安全

105 阅读6分钟

前言

阅读耗时四分钟

目录

八、并发线程安全.png

一、概念

安全高效的处理并发是 Rust 诞生的目的之一,主要解决的是服务器高负载承受能力。
并发(concurrent)的概念是指程序不同的部分独立执行,这与并行(parallel)的概念容易混淆,并行强调的是"同时执行"。
并发往往会造成并行。
Concurrent:程序的不同部分之间独立的执行
Parallel: 程序的不同部分同时运行
本文中的并发泛指 concurrentparallel

  • 多线程问题
    • 竞争状态,线程以不一致的顺序访问数据或资源
    • 死锁,两个线程彼此等待对方使用完所持有的资源,线程无法继续
    • 只在某些情况下发生的Bug,很难可靠地复制现象和修复

二、线程

实现

主要通过thread::spawn创建线程

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

输出

number 1 from main thread!
number 1 from spawned thread!
number 2 from spawned thread!
number 2 from main thread!
number 3 from main thread!
number 3 from spawned thread!
number 4 from spawned thread!
number 4 from main thread!
number 5 from spawned thread!

主线程执行完了,程序执行完了。如果想要和java一样,作为守护线程,可以用join 可以这样改

use std::time::Duration;
use std::thread;
fn main() {
    let handle = thread::spawn(||{
        for i in 1..10{
            println!("number {} from spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
    // handle.join().unwrap(); //会等handle线程执行完才会执行下面的
    for i in 1..5{
        println!("number {} from main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
    handle.join().unwrap(); //主线程执行完了,会等待handle线程执行完。
}

值得一提的是 move,转交所有权,之前说过就不介绍了,直接来个简单例子

use std::thread;
fn main(){
    let v = vec![1, 2, 3];
    let handle = thread::spawn(move ||{
       println!("Here's a vector: {:?}", v); // 所有权已经给线程里咯
    });
    //drop(v); 这行会报错,因为所有权已经通过move移交到线程里了。
    handle.join().unwrap();
}

三、消息发送

主要是通过Rust提供的channel,但是一旦将值的所有权移交给了channel,就无法使用了。

use std::thread;
use std::sync::mpsc;
fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move||{
        let val = String::from("hi");
        tx.send(val).unwrap();
        //println!("val is {}", val); //这里val会报错,因为所有权已经移交出去了,通过send
    });
    let received = rx.recv().unwrap(); //阻塞等待
    println!("Got {}" , received);
}

四、共享状态并发

主要是介绍,体现Mutex 互斥锁的作用。

use std::sync::Mutex;
fn main() {
    let m = Mutex::new(5);
    {
        let mut num = m.lock().unwrap();
        *num = 6;
    }//自动会解锁
    println!("m = {:?}", m);
}

多线程

fn main() {
    let counter = Rc::new(Mutex::new(0));
    let mut handles = vec![];
    for _ in 0..10{
        let counter = Rc::clone(&counter); 
        let handle = thread::spawn(move || { //这里会报错,因为Rc不支持多线程用法,必须要实现Send的Trait才支持
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }
    for handle in handles{
        handle.join().unwrap();
    }
    println!("Result : {}", *counter.lock().unwrap());
}

利用Arc进行多线程原子引用计数,修改为如下

use std::sync::{Mutex,Arc};
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 Trait的类型可在线程间转移所有权,但是Rc<T>没有实现Send,所以Rc<T>只能用于单线程场景。
实现了Sync的类型可以安全的被多个线程引用,基础类型都是Sync,但是Rc<T>RefCell<T>、Cell<T>不是Sync,Mutex<T>Sync的。

六、Unsafe Rust

Unsafe Rust中有四种情况不会被编译器进行内存安全检查

  • 解引用原始指针
  • 调用unsafe函数或者方法
  • 访问或者修改可变的静态变量
  • 实现unsafe trait

使用 unsafe关键字可以切换到 unsafe rust,开启一个块,里面放着unsafe代码。但是unsafe并没有关闭借用检查或者停用其它安全检查。
任何内存安全相关的错误必须留在unsafe块里,尽可能隔离unsafe代码,将其封装在安全的抽象里。
主要是编译器无法保证内存安全,通过人为代码来保证不简单,通过unsafe显示标记,定位问题将会快一点。

原始指针

fn main() {

    let mut num = 5;
    let r1 = &num as * const i32;//可变的原始指针
    let r2 = &mut num as * mut i32;//不可变得原始指针
    println!("r1 = {}", *r1); //解引用错误,报错,必须要在unsafe中解引用原始指针

    let address = 0x012345usize;
    let r = address as * const i32;
}

修改如下


fn main() {

    let mut num = 5;
    let r1 = &num as * const i32;//可变的原始指针
    let r2 = &mut num as * mut i32;//不可变得原始指针
    unsafe{
        println!("r1 = {}", *r1);
    }

    let address = 0x012345usize;
    let r = address as * const i32;
}

既然使用原始指针有点不安全,为什么要使用,主要作用还是为了和C语言进行接口交互。
调用unsafe函数或者方法

fn main() {
    dangerous();//报错,提示必须要在unsafe块中调用

}
unsafe fn dangerous(){}

修改如下

fn main() {
    unsafe{
        dangerous();
    }
}
unsafe fn dangerous(){}

从其他语言调用Rust函数,也是一种不安全操作

#[no_mangle] //禁止编译器改名
pub extern "C" fn call_from_c(){
    println!("Just called a rust func form C !")
}

访问或者修改可变的静态变量

static HELLO_WORLD:&str = "Hello World";
static mut COUNTER :u32 = 0;
fn add_to_count(inc: u32){
    unsafe{
        COUNTER += inc;
    }
}
fn main() {
    add_to_count(3);
    println!("{}", HELLO_WORLD);
    unsafe{
        println!("COUNTER:{}", COUNTER);
    }
}

实现unsafe trait
当某个trait中至少一个方法拥有编译器无法校验的不安全因素时,就称这个trait是不安全的

unsafe trait Foo {
}
unsafe impl Foo for i32 { 
}

七、线程实例

单线程服务器

rust_code.PNG

use std::{
    fs,
    io::{Read, Write},
    net::{TcpListener, TcpStream},
};
fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    //依次处理每个请求
    for stream in listener.incoming() {
        let _stream = stream.unwrap();
        handle_connection(_stream);
    }
}
fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    stream.read(&mut buffer).unwrap();
    println!("Request:{}", String::from_utf8_lossy(&buffer[..]));

    let get = b"GET / HTTP/1.1\r\n";
    let(status_line, filename) = if buffer.starts_with(get) {
        ("HTTP/1.1 200 OK\r\n\r\n{}", "hello.html")
    }else{
        ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
    };
    let contents = fs::read_to_string(filename).unwrap();
    let response = format!("{}{}", status_line, contents);
    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
   
}

多线程服务器

lib.rs

use std::thread;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
pub struct ThreadPool{
    workers:Vec<Worker>,
    sender: mpsc::Sender<Job>,
}

impl ThreadPool{
    ///Crete a new ThreadPool
    pub fn new(size: usize) -> ThreadPool{
        assert!(size > 0);
        let (sender, receiver) = mpsc::channel();
        let receiver = Arc::new(Mutex::new(receiver));
        let mut workers = Vec::with_capacity(size);
        for id in 0..size {
            workers.push(Worker::new(id, Arc::clone(&receiver)));
        }
        ThreadPool{workers, sender}
    }
    pub fn execute<F>(&self, f:F)
    where
    F: FnOnce() + Send + 'static,{
        let job = Box::new(f);
        self.sender.send(job).unwrap();
    }
}
struct Worker{
    id:usize,
    thread: thread::JoinHandle<()>,
}
type Job = Box<dyn FnBox + Send + 'static>;

impl Worker {
    fn new(id:usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker{
        let thread = thread::spawn(move || loop {
            while let Ok(job) =  receiver.lock().unwrap().recv(){
            
                println!("Worker {} got a job; excuting.", id);
                // (*job)();
                job.call_box();
            }
        });
        Worker { id, thread }
    }
}
trait FnBox{
    fn call_box(self: Box<Self>);
}
impl <F: FnOnce()> FnBox for F {
    fn call_box(self: Box<F>) {
        (*self)()
    }
}

main.rs

use std::{
    thread,
    fs,
    io::{Read, Write},
    net::{TcpListener, TcpStream},
};

use web::ThreadPool;
fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    let pool = ThreadPool::new(4);
    //依次处理每个请求
    for stream in listener.incoming() {
        let _stream = stream.unwrap();
        pool.execute(||{
            handle_connection(_stream);
        });
        thread::spawn(||{});
    }
}
fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    stream.read(&mut buffer).unwrap();
    println!("Request:{}", String::from_utf8_lossy(&buffer[..]));

    let get = b"GET / HTTP/1.1\r\n";
    let(status_line, filename) = if buffer.starts_with(get) {
        ("HTTP/1.1 200 OK\r\n\r\n{}", "hello.html")
    }else{
        ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
    };
    let contents = fs::read_to_string(filename).unwrap();
    let response = format!("{}{}", status_line, contents);
    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
   
}