当前位置: 首页 > news >正文

【跟小嘉学 Rust 编程】十六、无畏并发(Fearless Concurrency)

系列文章目录

【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据
【跟小嘉学 Rust 编程】六、枚举和模式匹配
【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目
【跟小嘉学 Rust 编程】八、常见的集合
【跟小嘉学 Rust 编程】九、错误处理(Error Handling)
【跟小嘉学 Rust 编程】十一、编写自动化测试
【跟小嘉学 Rust 编程】十二、构建一个命令行程序
【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包
【跟小嘉学 Rust 编程】十四、关于 Cargo 和 Crates.io
【跟小嘉学 Rust 编程】十五、智能指针(Smart Point)
【跟小嘉学 Rust 编程】十六、无畏并发(Fearless Concurrency)

文章目录

  • 系列文章目录
    • @[TOC](文章目录)
  • 前言
  • 一、进程(Process)与线程(Thread)
    • 1.1、进程(Process)
    • 1.2、线程(Thread)
      • 1.2.1、线程
      • 1.2.2、多线程可能导致的问题
      • 1.2.3、实现线程方式
  • 二、多线程开发
    • 2.1、使用Thread::spwan 创建线程函数
    • 2.2、使用JoinHandle的join 方法等待所有线程执行完毕
    • 2.3、使用 move 闭包
    • 2.3、使用消息传递
      • 2.3.1、使用消息传递
      • 2.3.2、通道(Channel)
        • 2.3.2.1、使用mpsc::channel 创建 Channel
        • 2.3.2.2、发送端 send 方法
        • 2.3.2.3、接收端的
        • 2.3.2.4、发送多个值和接收等待
        • 2.3.2.5、使用 clone 来创建多个发送者
    • 2.4、共享状态并发(Shared-State Concurrency)
      • 2.4.1、使用 Mutex 每次只允许一个线程来访问数据
        • 2.4.1.1、mutex介绍
        • 2.4.1.2、mutex的规则
        • 2.4.1.3、`Mutex<T>`的API
        • 2.4.1.4、在多线程中共享 Mutext 与原子引用计数
        • 2.4.1.5、RefCell/RC和Mutex/ARC
    • 2.5、通过 Send Trait 和Sync Trait 来扩展并发
      • 2.5.1、Send :允许线程间转移所有权
      • 2.5.2、Sync :允许线程间转移所有权
      • 2.5.3、手动实现 Send 和 Sync是不安全的
  • 总结

前言

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

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

主要教材参考 《The Rust Programming Language》


一、进程(Process)与线程(Thread)

1.1、进程(Process)

在大部分OS里面,代码运行在进程中,OS同时管理多个进程。

1.2、线程(Thread)

1.2.1、线程

在你程序,各自独立部分可以同时运行,运行这些独立部分的就是线程(Thread)。

多线程运行的好处:

  • 提升性能表现
  • 增加复杂性:无法保障各线程的执行顺序

1.2.2、多线程可能导致的问题

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

1.2.3、实现线程方式

  • 通过调用 OS的 API 来创建线程: 1:1 模型,需要较小的运行时
  • 语言自己实现的线程(绿色线程):M:N模型,需要更大的运行时

Rust 需要权衡运行时的支持:Rust 标准库仅提供 1:1 模型 的线程

二、多线程开发

2.1、使用Thread::spwan 创建线程函数

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

主线程执行完,其他线程还没执行完

2.2、使用JoinHandle的join 方法等待所有线程执行完毕

thread::spawn 函数返回的是 JoinHandle。JoinHandle 持有值的所有权,调用其 join 方法可以等待对应的其他线程的完成。

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

2.3、使用 move 闭包

move 闭包通常和 thread::spawn 一起使用,它允许你使用其他线程的数据,创建线程的时候把值的所有权从一个线程转移到另外一个线程。

范例:错误示范

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

此时存在有如下错误信息

error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function--> src/main.rs:6:32|
6 |     let handle = thread::spawn(|| {|                                ^^ may outlive borrowed value `v`
7 |         println!("Here's a vector: {:?}", v);|                                           - `v` is borrowed here|
note: function requires argument type to outlive `'static`--> src/main.rs:6:18|
6 |       let handle = thread::spawn(|| {|  __________________^
7 | |         println!("Here's a vector: {:?}", v);
8 | |     });| |______^
help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword|
6 |     let handle = thread::spawn(move || {|                                ++++

根据提示信息,我们可以使用 move 关键字来修改

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

2.3、使用消息传递

2.3.1、使用消息传递

一种很流行且能够保证安全并发的技术就是:消息传递,线程或者 Actor 通过彼此发送消息(数据)来进行通信。

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

Rust 使用标准库中的Channel来

2.3.2、通道(Channel)

Channel 包含发送端和接收端,调用发送端端方法发送数据,接收端会检查和接受到达的数据,如果其中一段被丢弃了,那么 通道就关闭了。

2.3.2.1、使用mpsc::channel 创建 Channel

mpsc(multiple producer single consumer),多个生产者,一个消费者,返回一个 tuple,里面元素分别是发送端和接收端。

use std::sync::mpsc;
use std::thread;fn main() {let (tx, rx) = mpsc::channel();thread::spawn(move || {let val = String::from("hi");tx.send(val).unwrap();});let recevied = rx.recv().unwrap();println!("Got :{}", recevied);
}

2.3.2.2、发送端 send 方法

参数要发送的数据:返回 Result<T,E>,如果有问题,就返回一个错误。 会移交所有权

2.3.2.3、接收端的

1、recv 方法

阻止当前线程执行,直到 Channel 中有值被送来,一旦有值被收到,就返回Result<T,E>,当发送端关闭就会收到一个错误

2、try_recv 方法

不会阻塞,立即返回 Result<T,E>,有数据达到,返回OK,里面包含数据,否则返回错误。

通常使用循环来检查 try_recv 的结果。

2.3.2.4、发送多个值和接收等待

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

2.3.2.5、使用 clone 来创建多个发送者

use std::sync::mpsc;
use std::thread;
use std::time::Duration;fn main() {let (tx, rx) = mpsc::channel();let tx1 = tx.clone();thread::spawn(move || {let vals = vec![String::from("hi"),String::from("from"),String::from("the"),String::from("thread"),];for val in vals {tx1.send(val).unwrap();thread::sleep(Duration::from_secs(1));}});thread::spawn(move || {let vals = vec![String::from("more"),String::from("messages"),String::from("for"),String::from("you"),];for val in vals {tx.send(val).unwrap();thread::sleep(Duration::from_secs(1));}});for received in rx {println!("Got: {}", received);}
}

2.4、共享状态并发(Shared-State Concurrency)

Rust 支持通过共享状态实现并发,Channel类似单所有权,一旦值的所有权转移给Channel 就无法使用了。而共享内存并发类似多所有权,多个线程可以同时访问同一块内存。

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

2.4.1.1、mutex介绍

Mutex 是 mutual exclusion(互斥锁),在同一时刻,Mutex 只允许一个线程来访问某些数据,想要访问数据,线程必须获取互斥锁(lock),lock数据结构是 mutex 的一部分,它能跟着谁对数据拥有独占访问权。

mutext 通常被描述为:通过锁来保护它所持有的数据

2.4.1.2、mutex的规则

  • 使用数据之前,必须尝试获取锁(lock)
  • 使用完 mutext 所保护的数据,必须对数据进行解锁,以便其他线程可以获取锁

2.4.1.3、Mutex<T>的API

使用 Mutex::new(数据) 来创建 Mutex,Mutex 是一个智能指针,访问数据前通过 lock 方法来获取锁:

  • 会阻塞当前线程
  • lock可能会失败
  • 返回的是 MutexGuard(智能指针,实现了Deref 和 Drop)
use std::sync::Mutex;fn main() {let m = Mutex::new(5);{let mut num = m.lock().unwrap();*num = 6;}println!("m = {:?}", m);
}

2.4.1.4、在多线程中共享 Mutext 与原子引用计数

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

该代码报错,因为我们使用了move 移动了所有权,所以其他线程使用会报错。

use std::sync::{Arc, Mutex};
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());
}

Arc<T> 原子引用计数

2.4.1.5、RefCell/RC和Mutex/ARC

  • Mutex 提供了内部可变性和 Cell 家族一样,
  • 使用 Refcell 来 改变RC 里面的内容
  • 使用 Mutex 来改变 ARC 里面的内容

使用 Mutex 存在死锁风险

2.5、通过 Send Trait 和Sync Trait 来扩展并发

Rust 语言的并发特性比较少,目前讲的并发特性都来自于标准库,无需局限于标准库的并发,可以自己实现并发。

在Rust里面有两个并发的概念

  • std::marker::Sync
  • std::marker::Send

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

Rust中几乎所有的类型都实现了 Send,实现了 Send Trait 类型可以在线程间转移所有权,但是RC<T> 没有实现 Send,它只适用单线程场景。

任何完全由 Send 类型组成的类型也被标记为 Send。

除了原始指针之外,几乎所有的基础类型都是 Send。

2.5.2、Sync :允许线程间转移所有权

实现 Sync 类型可以完全被多个线程引用,如果 T 是 Sync,那么 &T 就是Send,引用可以被安全的送往另个线程。

基础类型都是 Sync, 完全由 Sync 类型组成的类型也是 Sync,但是 RC不是Sync, RefCell Cell 也不是Sync,Mutex 是 Sync

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

总结

以上就是今天要讲的内容

相关文章:

【跟小嘉学 Rust 编程】十六、无畏并发(Fearless Concurrency)

系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…...

Android 进阶——图形显示系统之VSync和 Choreographer的创建详解(一)

引言 前一篇文章Android 进阶——图形显示系统之底层图像显示原理小结(一)介绍了关于Android 图形显示系统的基础理论,相信你对于Android的图形显示系统中图形界面渲染刷新机制有了更深的了解,接下来进一步讲解VSync和Choreography的联系和作用。 一、VSync 信号的产生概…...

SQL Server开启变更数据捕获(CDC)

一、CDC简介 变更数据捕获&#xff08;Change Data Capture &#xff0c;简称 CDC&#xff09;&#xff1a;记录 SQL Server 表的插入、更新和删除操作。开启cdc的源表在插入、更新和删除操作时会插入数据到日志表中。cdc通过捕获进程将变更数据捕获到变更表中&#xff0c;通过…...

八、性能测试

八、性能测试 8.1 性能测试代码 #include"ConcurrentAlloc.h"// ntimes 一轮申请和释放内存的次数 // rounds 轮次 void BenchmarkMalloc(size_t ntimes, size_t nworks, size_t rounds) {std::vector<std::thread> vthread(nworks);std::atomic<size_t&g…...

景芯SoC 芯片全流程培训

【全网唯一】景芯SoC是一款用于芯片全流程培训的低功耗ISP图像处理SoC&#xff0c;采用低功耗RISC-V处理器&#xff0c;内置ITCM SRAM、DTCM SRAM&#xff0c;集成包括MIPI、ISP、CNN、QSPI、UART、I2C、GPIO、百兆以太网等IP&#xff0c;采用SMIC40工艺设计流片。 培训数据包括…...

目标检测后的图像上绘制边界框和标签

效果如图所示&#xff0c;有个遗憾就是CV2在图像上显示中文有点难&#xff0c;也不想用别的了&#xff0c;所以改成了英文&#xff0c;代码在下面了&#xff0c;一定要注意一点&#xff0c;就是标注文件的读取一定要根据自己的实际情况改一下&#xff0c;我的所有图像的标注文件…...

Leetcode: 1. 两数之和 【题解超详细】

前言 有人夜里挑灯看花&#xff0c;有人相爱&#xff0c;有人夜里开车看海&#xff0c;有人leetcode第一题都做不出来。 希望下面的题解可以帮助你们开始 你们的 leetcode 刷题 的 天降之路 题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中…...

PHP 通过 Redis 解决并发请求的操作问题

比如PHP收到两个并发的请求A和B&#xff0c;要求只能其中一个请求处理S1操作&#xff0c;另一个请求直接返回失败&#xff0c;可以通过redis去解决&#xff1a; SETNX&#xff08;SET if Not eXists&#xff09;是 Redis 中的一个原子命令&#xff0c;用于设置键-值对&#xf…...

浅谈信息论和信息编码

目录 背景 信息是什么 信息度量 小白鼠实验 哈夫曼编码 密码学 其它应用 背景 克劳德艾尔伍德香农&#xff08;Claude Elwood Shannon&#xff09;出生于 1916 年 美国密歇根州。1936 年毕业于密歇根大学&#xff0c;获得数学和电子工程学士学位。之后&#xff0c;他在麻…...

【测试】笔试02

文章目录 1. 下面不属于软件测试步骤的是2. 关于测试驱动开发&#xff0c;描述错误的是3. 在软件测试中&#xff0c;圈复杂度&#xff08;Cyclomatic complexity&#xff09;&#xff1a;代码逻辑复杂度的度量&#xff0c;提供了被测代码的路径数量。圈复杂度可通过系统控制流图…...

公司内部网段多管控乱,该如何规范跨网文件传输交换?

古往今来&#xff0c;高筑墙一直是有效的防御措施。从边塞长城到护城河外的高高城墙&#xff0c;都是利用隔离地域的形式实现保护安全域的效果。这样一来&#xff0c;城内的安全域可以在遇到危险时受到有效保护。 在企业网络安全防护方面&#xff0c;网络安全域隔离也是网络安全…...

Ceph入门到精通-OSD waring 设置建议

OSD 以下检查表明 OSD 节点存在问题。 警告 1 在 /var/lib/ceph/osd 中找到的多个ceph_fsid值。 这可能意味着您正在托管许多集群的 OSD 此节点或某些 OSD 配置错误以加入 您期望的集群。 2 设置可能会导致数据丢失&#xff0c;因为如果 未达到最小值&#xff0c;Ceph 将不会确…...

软件测试工程师如何快速理解业务?

1. 阅读需求文档和业务资料 仔细阅读与业务相关的文档和资料对于理解业务至关重要。 需求文档通常描述了软件的功能和用户需求&#xff0c;而业务规范则详细说明了业务流程、规则和标准。 仔细阅读这些文档&#xff0c;你可以了解业务的基本概念、要求和流程。 同时&#x…...

【教程】部署apprtc服务中安装google-cloud-cli组件的问题及解决

#0# 前置条件 已经安装完成node&#xff0c;grunt&#xff0c;node 组件和python pip包等。需要安装google-cloud-cli组件。 Ubuntu安装google-cloud-cli组件 apprtc项目运行需要google-cloud-cli前置组件&#xff0c;且运行其中的dev_appserver.py。 根据google官方的关于安…...

C++——shared_ptr:make_shared的用处,与shared_ptr直接构造的区别

shared_ptr shared_ptr继承自__shared_ptr&#xff0c;其中有两个对象&#xff0c;一个是指向资源的指针&#xff0c;一个是控制块&#xff0c;指向一个引用计数对象。控制块中存储了强引用和弱引用的计数&#xff0c;强引用Uses代表shared_ptr对象的引用计数&#xff0c;弱引…...

【网络安全带你练爬虫-100练】第17练:分割字符串

目录 一、目标1&#xff1a;使用函数分割 二、目标2&#xff1a;使用函数模块 三、目标3&#xff1a;使用正则匹配 一、目标1&#xff1a;使用函数分割 目标&#xff1a;x.x.x.x[中国北京 xx云] 方法&#xff1a;split函数replace函数 1、分割&#xff1a;使用split()方法将…...

Unity 之ToolTip的用法

文章目录 在Unity中&#xff0c;ToolTip是一个在编辑器中使用的UI元素&#xff0c;它提供了鼠标悬停在某个对象或控件上时显示的文本信息。ToolTip通常用于向开发人员提供有关对象、字段、控件或菜单项的附加信息&#xff0c;从而帮助他们更好地理解和使用这些元素。 ToolTip通…...

xsschallenge通关(11-15)

level 11 老规矩&#xff0c;先查看源码&#xff0c;做代码审计&#xff1a; <?php ini_set("display_errors", 0); $str $_GET["keyword"]; $str00 $_GET["t_sort"]; $str11$_SERVER[HTTP_REFERER]; $str22str_replace(">&quo…...

Kubernetes技术--k8s核心技术集群的安全机制RBAC

1.引入 我们在访问k8s的集群的时候,需要经过一下几个步骤: -a:认证 -1).传输安全:对外是不暴露端口:8080,只能够在内部访问,对外使用的是6443端口。 -2).客户端认证的常用几种方式: -https证书 基于ca证书 -https token认证 通过token识别用户 -https <...

【JavaSE】String类

两种创建String对象的区别 String s1 "hello"; String s2 new String("hello");s1是先查看常量池是否有 “hello” 数据空间&#xff0c;如果有就直接指向它&#xff0c;如果没有就创建然后指向它。s1最终指向的是常量池的空间地址。 s2是先在堆中创建空…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

DBLP数据库是什么?

DBLP&#xff08;Digital Bibliography & Library Project&#xff09;Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高&#xff0c;数据库文献更新速度很快&#xff0c;很好地反映了国际计算机科学学术研…...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例

目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码&#xff1a;冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...

二维FDTD算法仿真

二维FDTD算法仿真&#xff0c;并带完全匹配层&#xff0c;输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...