23.并发

news2025/2/24 23:57:58

目录

  • 一、一些概念
  • 二、进程和线程
    • 2.1 概念
    • 2.2 多线程导致的问题
    • 2.3 使用spawn创建新线程
    • 2.4 线程与move闭包
  • 三、消息传递
    • 3.1 概念
    • 3.2 创建通道
    • 3.3 示例
    • 3.4 其它测试
  • 四、共享状态并发
    • 4.1 互斥器
    • 4.2 Mutex的API
    • 4.3 多线程共享Mutex
      • 1)在多线程之间共享值;
      • 2)多线程和多所有权
      • 3) 原子引用计数
      • 4)RefCell/Rc与 Mutex/Arc的相似性
  • 五、使用Sync和Send Trait的可扩展并发
    • 5.1 send
    • 5.2 Sync

一、一些概念

  • 并发编程(Concurrent programming):程序的不同部分相互独立运行;
  • 并行编译(parallel programming):程序不同部分同时运行;
  • 这里将以上两个概念统称为并发,而不做更加细致的区分;

二、进程和线程

2.1 概念

  • 进程(process):在大部分现代OS中,已执行的代码运行在一个进程中,OS管理多个进程;
  • 线程(Thread):在程序内部同时运行的多个独立部分;

2.2 多线程导致的问题

  • 线程是同时运行的,所以无法预先线程的执行顺序,因此导致以下问题:
    • 竞争状态(Race conditions):多个线程以不一致的顺序访问数据或资源;
    • 死锁(Deadlocks):两个线程相互等待对方所持有的资源;
    • 只在某些情况下发生BUG,很难复现和修复;

2.3 使用spawn创建新线程

  • 调用thread::spawn函数并传递一个新线程运行的代码闭包;
use std::thread;
use std::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));
    }
}

程序运行结果如下
在这里插入图片描述

  • 主线程和子线程一起运行,当主线程结束之后无论子线程是否结束,子线程都会被强制结束;
  • 使用thread::spawn的返回值的join()方法可以保证线程执行完成;
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();
}
  • 通过handle的join方法会阻止当前运行的线程,直到handle所代表的线程运行结束;
    在这里插入图片描述
  • 把join()方法移动到最后一个for之前,像下面这样
fn main(){
    ……
    handle.join().unwrap();
    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}

则打印如下
在这里插入图片描述

  • 可见,这是等待子线程运行结束后才会开始运行主线程;

2.4 线程与move闭包

  • move闭包通过与thread::spawn一起使用,它允许在一个线程中使用另一个线程的数据;
  • 创建新线程时,可以使用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);  //所有权被转移
    handle.join().unwrap();
}
  • 通过move关键字,将变量v的所有权进行转移;
  • 转移之后主线程里的v就不能再使用了;

三、消息传递

3.1 概念

  • 消息传递(message passing) 是一个日益流行且安全的并发的方式;
  • 通道(channel) 是Rust中实现消息传递并发的主要工具;
  • 通道由两部分组成:一个发送者(transmitter)和一个接收者(receiver);

3.2 创建通道

  • 使用mpsc::channel函数创建新通道,函数返回一个元组,分别表示发送端和接收端;
  • mpsc是多个生产者,单个消费者(multiple producer,single consumer)的缩写;

3.3 示例

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();
    });

    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}
  • 使用mpsc::channel()创建通道;
  • 使用move将发送者tx的所有权转移到子线程中;
  • 发送端:
    • 发送端的send方法用来获取需要放入通道的值;
    • send方法返回一个Result<T, E>类型,这里发送失败时调用unwrap产生panic;
  • 接收端:
    • 接收端有两个接收方法:recvtry_recv
    • recv会阻塞主线程执行直到从通道中接收一个值,返回Result<T, E>,发送端关闭时收到一个错误;
    • try_recv不会阻塞,立即Result<T, E>

3.4 其它测试

通道与所有权转移

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);
    });

    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}
  • 代码尝试在发送端的send之后将值打印出来,提示所有权已经发生了移动;

在这里插入图片描述

发送多个值并观察接收者在等待

use std::thread;
use std::sync::mpsc;
use std::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_secs(1));
        }
    });

    for received in rx {
        println!("Got: {}", received);
    }
}
  • 子线程循环发送一些字符串;
  • 主线程不再显式调用recv函数,而是将rx当作迭代器使用,循环输出接收到的值;
  • 主线程的for循环里没有任何暂停的代码,因此它一直在等待从子线程发送的值;

通过克隆创建多个发送者

use std::thread;
use std::sync::mpsc;
use std::time::Duration;

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

    let tx1 = tx.clone();

    thread::spawn(move || {
        let vals = vec![
            String::from("tx1: hi"),
            String::from("tx1: from"),
            String::from("tx1: the"),
            String::from("tx1: thread"),
        ];
    
        for val in vals {
            tx1.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });
    
    thread::spawn(move || {
        let vals = vec![
            String::from("tx: more"),
            String::from("tx: messages"),
            String::from("tx: for"),
            String::from("tx: you"),
        ];
    
        for val in vals {
            tx.send(val).unwrap();
            thread::sleep(Duration::from_secs(1));
        }
    });
    
    for received in rx {
        println!("Got: {}", received);
    }
}
  • 发送之前调用发送端的clone方法再创建一个发送端;
  • 两个发送端者往通道里写入,接收端接收,从下面的结果中可以看到两个发送端的数据被交替接收到了;
    在这里插入图片描述

四、共享状态并发

  • Rust支持通过共享状态实现并发;
  • 通道类似于单所有权:一旦将值传送到通道中,就无法再次使用这个值;
  • 共享内存并发类似多所有权:多个线程可以同时访问同一块内存;

4.1 互斥器

  • 互斥器(mutex)只允许一个线程访问某些数据;
  • 使用互斥器:
    • 在使用数据之前尝试获取互斥锁;
    • 处理完被互斥器所保护的数据之后,必须解锁;

4.2 Mutex的API

  • 通过Mutex::new创建Mutex<T>,这是一个智能指针;
  • 使用lock方法获取锁,此方法会阻塞当前线程,直接获取锁为止;
  • 当拥有锁的线程panic了,lock会失败;
  • lock的返回值是MutexGuard的智能指针,离开作用域时,自动解锁;
use std::sync::Mutex;

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

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

    println!("m = {:?}", m);
}
  • 调用Mutex::new创建一个Mutex,其中的5是要保护的数据;
  • 调用lock获取锁,使用 unwrap()处理panic的情况;
  • 离开内部作用域后自动解锁;
    在这里插入图片描述

4.3 多线程共享Mutex

1)在多线程之间共享值;

use std::sync::Mutex;
use std::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());
}
  • 创建一个要保护的计数器counter,初始值是0和空的Vector;
  • 创建10个子线程,将每个线程句柄放到Vector中,线程内部获取要保护的值并加1;
  • 离开子线程作用域后自动释放锁资源;
  • 最后在主线程中打印counter的值;
    在这里插入图片描述
  • value moved into closure here, in previous iteration of loop表示所有权已经在上一次循环中移动过了,因此这里不能再移动;

2)多线程和多所有权

  • Rc<T>智能指针可以拥有多所有者;
  • Mutex<T>封装进Rc<T>
use std::rc::Rc;
use std::sync::Mutex;
use std::thread;

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 || {
            let mut num = counter.lock().unwrap();

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

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

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

编译报错
在这里插入图片描述

  • 编译器提示:线程里Rc<T>不能被安全的发送;
  • 之前提过Rc<T>只能用在单线程中;
  • 多线程中使用原子引用计数

3) 原子引用计数

  • 原子引用计数Arc<T>是一个类似Rc<T>并可以安全的用于并发环境的类型;
  • 封装在std::sync::atomic中;
  • Arc<T>Rc<T>的API相同;
use std::sync::{Mutex, Arc};
use std::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());
}
  • 输出正确结果:10

4)RefCell/Rc与 Mutex/Arc的相似性

  • 和Cell家族一样,Mutex<T>提供了内部可变性;
  • 可以使用RefCell<T>改变Rc<T>里的内容;
  • 可以使用Mutex<T>改变Arc<T>里的内容;
  • Mutex<T>有死锁风险

五、使用Sync和Send Trait的可扩展并发

  • Rust语言本身的并发性较少,目前的并发特性都来自于标准库;
  • 无需局限于标准库的并发,可以自己实现;
  • Rust语言中有两个并发概念:std::marker::Syncstd::marker::Send两个trait;

5.1 send

  • Send标记的trait表明所有权可以在线程间传递;
  • 几乎所有的Rust类型都是Send的;
  • Rc<T>没有实现Send,它只适合单线程;
  • 任何完全由Send类型组成的类型也被标记为Send;
  • 除了原始指针之外,几乎所有的基础类型都是Send;

5.2 Sync

  • Sync标记的trait可以安全的被多个线程引用;
  • 如果T是Sync,那么&T就是Send,这意味着引用可以安全的发送到另一个线程;
  • 基础类型都是Sync
  • 完全由Sync类型组成的类型也是Sync,但Rc<T>不是Sync的,RefCell<T>Cell<T>家族也不是Sync的;

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1854549.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ARC学习(3)基本编程模型认识(三)

笔者来介绍arc的编程模型的中断流程和异常流程 1、中断介绍 主要介绍一下中断进入的流程&#xff0c;包括需要配置的寄存器等信息。 中断号&#xff1a;16-255&#xff0c;总共240个中断。触发类型&#xff1a;脉冲或者电平触发中断优先级&#xff1a;16个&#xff0c;0最大&…

在linux系统中使用docker、mysql实例

systemctl 是一个命令行工具&#xff0c;用于控制和管理基于 systemd 的 Linux 发行版中的系统和服务。 启动服务 &#xff1a;使用 systemctl start [service-name] 开始一个服务。 如启动docker&#xff1a;systemctl start docker 停止服务 &#xff1a;使用 systemctl st…

【python】python葡萄酒国家分布情况数据分析pyecharts可视化(源码+数据集+论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

力扣SQL50 超过5名学生的课

Problem: 596. 超过5名学生的课 Code select class from courses group by class having count(distinct student) > 5;

FFmpeg源码:ff_ctz / ff_ctz_c函数分析

一、ff_ctz函数的作用 ff_ctz定义在FFmpeg源码目录的libavutil/intmath.h 下&#xff1a; #ifndef ff_ctz #define ff_ctz ff_ctz_c /*** Trailing zero bit count.** param v input value. If v is 0, the result is undefined.* return the number of trailing 0-bits*/…

如何使用AI工具进行写作

随着AI人工智能技术的飞速发展&#xff0c;AI工具已经逐渐成为学术和专业写作的得力助手。AI工具不仅可以帮助我们提高写作效率&#xff0c;还能在内容创作上提供灵感和支持。在本文中&#xff0c;小编将和大家分享如何利用AI工具提高写作效率和质量&#xff0c;并确保文章的原…

基于CDMA的多用户水下无线光通信(3)——解相关多用户检测

继续上一篇博文&#xff0c;本文将介绍基于解相关的多用户检测算法。解相关检测器的优点是因不需要估计各个用户的接收信号幅值而具有抗远近效应的能力。常规的解相关检测器有运算量大和实时性差的缺点&#xff0c;本文针对异步CDMA的MAI主要来自干扰用户的相邻三个比特周期的特…

活用变量,让Postman的使用飞起来

在 Postman 中使用变量是一种非常强大的功能&#xff0c;它可以极大地增强 API 测试和开发的灵活性和效率。 Postman变量的类型 变量在 Postman 中可以在多个层次设置和使用&#xff0c;包括 全局变量环境变量集合变量局部变量&#xff08;如在脚本中暂时创建的变量&#xf…

使用MyBatis Generator自动代码生成器简化Java持久层开发

在Web开发中&#xff0c;数据访问层&#xff08;DAO层&#xff09;的编码工作往往重复且繁琐&#xff0c;尤其是在处理数据库表与Java对象之间的映射时。MyBatis Generator是一款强大的代码生成工具&#xff0c;它能自动生成DAO接口、Mapper XML文件和实体类&#xff0c;极大地…

【python】python股票量化交易策略分析可视化(源码+数据集+论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

机器学习基础:与Python关系和未来发展

目录 初识Python Python的由来 自由软件运动 编译方式的演进 Python语言的特点 语法简单&#xff0c;易于理解 语法结构清晰&#xff0c;快速上手 丰富的第三方库 机器学习 监督学习 无监督学习 半监督学习 欢迎回到我们的神经网络与深度学习Tensorflow实战学习&am…

算法学习014 0-1背包问题 c++动态规划算法实现 中小学算法思维学习 信奥算法解析

目录 C0-1背包 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、运行结果 五、考点分析 六、推荐资料 C0-1背包 一、题目要求 1、编程实现 有 N 件物品和一个容量为 M的背包&#xff0c;每件物品有各自的价值且只能被选择一次&#xff0c;要求…

【从0实现React18】 (四) 如何触发更新 带你了解react触发更新的流程以及更新后如何触发render

常见的触发更新的方式 创建 React 应用的根对象 ReactDOM.creatRoot().render()&#xff1b;类组件 this.setState()&#xff1b;函数组件 useState useEffect&#xff1b; 我们希望实现一套统一的更新机制&#xff0c;他的特点是&#xff1a; 兼容上述触发更新的方式方便后续…

Vienna 整流器的基本原理及数学模型

2.1 Vienna 整流器基本工作原理 2.1.1 主电路拓扑结构分析 Vienna 整流器系统的主电路包含用于升压的三相电感、三相桥臂和两个直流侧均压电容。通过有规律的对双向开关进行控制不仅能实现功率双向流动&#xff0c;还能使网侧电流时刻跟踪电网电压&#xff0c;使系统运行在高…

【ajax实战01】数据管理网站总述

一&#xff1a;功能实现 登录和权限判断查看文章内容列表&#xff08;筛选和分页&#xff09;编辑文章&#xff08;数据回显&#xff09;删除文章发布文章&#xff08;图片上传&#xff0c;富文本编辑器&#xff09; 该网站最终实现&#xff1a;登录后台管理系统&#xff0c;…

【Linux进程】进程的 切换 与 调度(图形化解析,小白一看就懂!!!)

目录 &#x1f525;前言&#x1f525; &#x1f4a7;进程切换&#x1f4a7; &#x1f4a7;进程调度&#x1f4a7; &#x1f525;总结与提炼&#x1f525; &#x1f525;共勉&#x1f525; &#x1f525;前言&#x1f525; 在 Linux 操作系统中&#xff0c;进程的 调度 与 …

Git使用过程中涉及的几个区域

一. 简介 Git 是一个开源的分布式版本控制系统&#xff0c;可以有效、高速的处理从很小到非常大的项目版本管理&#xff0c;也是 Linus Torvalds 为了帮助管理 Linux内核开发而开发的一个开放源码的版本控制软件。 本文简单了解一下 git涉及的几个部分&#xff0c;以及git 常…

使用Flink CDC实时监控MySQL数据库变更

在现代数据架构中&#xff0c;实时数据处理变得越来越重要。Flink CDC&#xff08;Change Data Capture&#xff09;是一种强大的工具&#xff0c;可以帮助我们实时捕获数据库的变更&#xff0c;并进行处理。本文将介绍如何使用Flink CDC从MySQL数据库中读取变更数据&#xff0…

leetcode 二分查找·系统掌握 搜索二维矩阵

题目&#xff1a; 题解&#xff1a; 一个可行的思路是使用~01~泛型对每一行的最后一个元素进行查找找到第一个大于等于target的那一行&#xff0c;判断查找结果如果“失败”返回false否则继续在改行进行常规二分查找target的值根据查找结果返回即可。 bool searchMatrix(vec…

基于Quartus Prime18.1的安装与FPGA的基础仿真(联合Modelsim)教程

Quartus是一种美国科技公司Intel&#xff08;英特尔&#xff09;公司开发的FPGA&#xff08;现场可编辑门阵列&#xff09;设计编译软件&#xff0c;用作设计、仿真、综合和布局、支持多种编程语言&#xff0c;包括VHDL、Verilog等&#xff0c;并具有丰富的功能和工具库&#x…