Rust 程序设计语言学习——并发编程
安全且高效地处理并发编程是 Rust 的另一个主要目标。并发编程(Concurrent programming),代表程序的不同部分相互独立地执行,而并行编程(parallel programming)代表程序不同部分同时执行,这两个概念随着计算机越来越多的利用多处理器的优势而显得愈发重要。由于历史原因,在此类上下文中编程一直是困难且容易出错的:Rust 希望能改变这一点。
起初,Rust 团队认为确保内存安全和防止并发问题是两个分别需要不同方法应对的挑战。随着时间的推移,团队发现所有权和类型系统是一系列解决内存安全和并发问题的强有力的工具!通过利用所有权和类型检查,在 Rust 中很多并发错误都是编译时错误,而非运行时错误。因此,相比花费大量时间尝试重现运行时并发 bug 出现的特定情况,Rust 会拒绝编译不正确的代码并提供解释问题的错误信息。因此,你可以在开发时修复代码,而不是在部署到生产环境后修复代码。
一、线程
在大部分现代操作系统中,已执行程序的代码在一个进程(process)中运行,操作系统则会负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这些运行这些独立部分的功能被称为线程(threads)。
将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题:
- 竞态条件(Race conditions),多个线程以不一致的顺序访问数据或资源
- 死锁(Deadlocks),两个线程相互等待对方,这会阻止两者继续运行
- 只会发生在特定情况且难以稳定重现和修复的 bug
Rust 尝试减轻使用线程的负面影响。不过在多线程上下文中编程仍需格外小心,同时其所要求的代码结构也不同于运行于单线程的程序。
编程语言有一些不同的方法来实现线程,而且很多操作系统提供了创建新线程的 API。Rust 标准库使用 1:1 线程实现,这代表程序的每一个语言级线程使用一个系统线程。
在 Rust 中,你可以使用 std::thread::spawn
来创建多个线程。以下是一个创建多个线程并让它们执行不同任务的例子:
use std::thread;
use std::time::Duration;fn main() {// 创建一个向量来存储JoinHandlelet mut handles: Vec<thread::JoinHandle<()>> = Vec::new();// 创建并启动多个线程for i in 0..5 {let handle = thread::spawn(move || {// 打印线程编号println!("Thread number {} is running", i);// 模拟一些工作thread::sleep(Duration::from_millis(500 * i));// 打印线程完成的消息println!("Thread number {} finished", i);});// 将JoinHandle存储在向量中handles.push(handle);}// 等待所有线程结束for handle in handles {handle.join().unwrap();}println!("All threads have finished execution.");
}
在这个例子中,我们首先创建了一个 Vec<thread::JoinHandle<()>>
来存储每个线程的 JoinHandle
。然后,我们使用一个 for
循环来创建 5 个线程。每个线程都执行一个移动(move
)闭包,该闭包打印线程编号,休眠一段时间,然后打印完成消息。移动闭包通过 move
关键字捕获循环变量 i
的值,使得每个线程都有自己的 i
副本。
在所有线程创建之后,我们遍历 handles
向量,调用每个 JoinHandle
的 join
方法。这会阻塞主线程直到对应的线程结束。如果线程成功结束,join
方法会返回 ()
。如果线程由于 panic 而结束,join
方法会返回一个错误,这里我们使用 unwrap
来获取结果并忽略潜在的错误。最后,当所有线程都完成后,我们在主线程中打印一条消息。
请注意,在实际应用中,你可能需要更细致地处理 join
方法返回的结果,以确保能够适当地响应线程中的 panic。
二、消息传递
一个日益流行的确保安全并发的方式是消息传递(message passing),这里线程或 actor 通过发送包含数据的消息来相互沟通。这个思想来源于 Go 编程语言文档中 的口号:“不要通过共享内存来通讯;而是通过通讯来共享内存”。
为了实现消息传递并发,Rust 标准库提供了一个信道(channel)实现。信道是一个通用编程概念,表示数据从一个线程发送到另一个线程。编程中的信息渠道(信道)有两部分组成,一个发送者(transmitter)和一个接收者(receiver)。代码中的一部分调用发送者的方法以及希望发送的数据,另一部分则检查接收端收到的消息。当发送者或接收者任一被丢弃时可以认为信道被关闭(closed)了。
mpsc::channel
函数创建一个新的信道;mpsc
是多个生产者,单个消费者(multiple producer, single consumer)的缩写。简而言之,Rust 标准库实现信道的方式意味着一个信道可以有多个产生值的发送(sending)端,但只能有一个消费这些值的接收(receiving)端。
mpsc::channel
函数返回一个元组:第一个元素是发送端,而第二个元素是接收端。由于历史原因,tx
和 rx
通常作为发送者(transmitter)和接收者(receiver)的缩写。
信道的发送端有一个 send
方法用来获取需要放入信道的值。send
方法返回一个 Result<T, E>
类型,所以如果接收端已经被丢弃了,将没有发送值的目标,所以发送操作会返回错误。
信道的接收者有两个有用的方法:recv
和 try_recv
。 recv
是 receive
的缩写。这个方法会阻塞线程执行直到从信道中接收一个值。一旦发送了一个值,recv
会在一个 Result<T, E>
中返回它。当信道发送端关闭,recv
会返回一个错误表明不会再有新的值到来了。
try_recv
不会阻塞,相反它立刻返回一个 Result<T, E>
:Ok
值包含可用的信息,而 Err
值代表此时没有任何消息。如果线程在等待消息过程中还有其他工作时使用 try_recv
很有用:可以编写一个循环来频繁调用 try_recv
,在有可用消息时进行处理,其余时候则处理一会其他工作直到再次检查。
2.1 信道与所有权转移
这里尝试在通过 tx.send
发送 data
到信道中之后将其打印出来。允许这么做是一个坏主意:一旦将值发送到另一个线程后,那个线程可能会在我们再次使用它之前就将其修改或者丢弃。其他线程对值可能的修改会由于不一致或不存在的数据而导致错误或意外的结果。
use std::sync::mpsc;
use std::thread;fn main() {let (tx, rx) = mpsc::channel();let sender_thread = thread::spawn(move || {let data = "Hello from sender!".to_string();tx.send(data).unwrap();println!("Sended: {}", data);});let receiver_thread = thread::spawn(move || {let received = rx.recv().unwrap();println!("Received: {}", received);});sender_thread.join().unwrap();receiver_thread.join().unwrap();
}
编译这一段代码会报错:
Compiling playground v0.0.1 (/playground)
error[E0382]: borrow of moved value: `data`--> src/main.rs:10:32|
8 | let data = "Hello from sender!".to_string();| ---- move occurs because `data` has type `String`, which does not implement the `Copy` trait
9 | tx.send(data).unwrap();| ---- value moved here
10 | println!("Sended: {}", data);| ^^^^ value borrowed here after move|= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)For more information about this error, try `rustc --explain E0382`.
error: could not compile `playground` (bin "playground") due to 1 previous error
我们的并发错误会造成一个编译时错误。send
函数获取其参数的所有权并移动这个值归接收者所有。这可以防止在发送后再次意外地使用这个值;所有权系统检查一切是否合乎规则。
修正这段代码,就是把 tx.send
之后的打印语句去除,程序就可以正常运行了。
use std::sync::mpsc;
use std::thread;fn main() {let (tx, rx) = mpsc::channel();let sender_thread = thread::spawn(move || {let data = "Hello from sender!".to_string();tx.send(data).unwrap();});let receiver_thread = thread::spawn(move || {let received = rx.recv().unwrap();println!("Received: {}", received);});sender_thread.join().unwrap();receiver_thread.join().unwrap();
}
运行结果
Received: Hello from sender!
在这个示例中,我们创建了一个信道 (tx, rx)
,tx
是发送端,rx
是接收端。我们创建了一个线程 sender_thread
来发送数据,并将数据的所有权通过 send
方法转移给接收端。接收端通过 recv
方法接收数据。
2.2 发送多个值并观察接收者的等待
use std::sync::mpsc;
use std::thread;fn main() {let (tx, rx) = mpsc::channel();// 创建一个线程发送多个值let sender_thread = thread::spawn(move || {for i in 1..=5 {tx.send(i).unwrap();println!("Sent {}", i);}});// 创建一个线程接收值let receiver_thread = thread::spawn(move || {for _ in 1..=5 {let received = rx.recv().unwrap();println!("Received {}", received);}});sender_thread.join().unwrap();receiver_thread.join().unwrap();
}
运行结果
Sent 1
Sent 2
Sent 3
Sent 4
Sent 5
Received 1
Received 2
Received 3
Received 4
Received 5
在这个示例中,我们创建了一个线程发送一系列值(1 到 5),并在发送每个值后打印一条消息。接收端在接收到所有值之前会阻塞等待。
2.3 通过克隆发送者来创建多个生产者
use std::sync::mpsc;
use std::thread;fn main() {let (tx, rx) = mpsc::channel();// 克隆发送端以创建多个生产者let tx_clone = tx.clone();let sender_thread1 = thread::spawn(move || {for i in 1..=5 {tx.send(i).unwrap();println!("Sender 1 sent {}", i);}});let sender_thread2 = thread::spawn(move || {for i in 5..=10 {tx_clone.send(i).unwrap();println!("Sender 2 sent {}", i);}});// 接收端let receiver_thread = thread::spawn(move || {let mut values = Vec::new();for _ in 1..=10 {values.push(rx.recv().unwrap());}println!("Received all values: {:?}", values);});sender_thread1.join().unwrap();sender_thread2.join().unwrap();receiver_thread.join().unwrap();
}
运行结果
Sender 1 sent 1
Sender 1 sent 2
Sender 2 sent 5
Sender 2 sent 6
Sender 2 sent 7
Sender 2 sent 8
Sender 2 sent 9
Sender 2 sent 10
Sender 1 sent 3
Sender 1 sent 4
Sender 1 sent 5
Received all values: [1, 5, 2, 6, 7, 8, 9, 10, 3, 4]
在这个示例中,我们克隆了发送端 tx
来创建两个生产者 sender_thread1
和 sender_thread2
。每个生产者都发送一系列值,而接收端接收所有值并将它们存储在一个向量中。
请注意,在实际应用中,你可能需要考虑使用更高级的并发原语,如 std::sync::Arc
来共享状态,或者使用 std::sync::Barrier
来同步线程。此外,错误处理在多线程程序中也非常重要,这里为了示例的简洁性,我们使用了 unwrap
来处理可能的错误。在生产代码中,你应该更细致地处理这些情况。
三、共享状态
在某种程度上,任何编程语言中的信道都类似于单所有权,因为一旦将一个值传送到信道中,将无法再使用这个值。共享内存类似于多所有权:多个线程可以同时访问相同的内存位置。
互斥锁
互斥锁(mutex)是 mutual exclusion 的缩写,也就是说,任意时刻,其只允许一个线程访问某些数据。为了访问互斥锁中的数据,线程首先需要通过获取互斥锁的锁(lock)来表明其希望访问数据。锁是一个作为互斥锁一部分的数据结构,它记录谁有数据的排他访问权。因此,我们描述互斥锁为通过锁系统保护(guarding)其数据。
互斥锁以难以使用著称,因为你不得不记住:
- 在使用数据之前尝试获取锁。
- 处理完被互斥锁所保护的数据之后,必须解锁数据,这样其他线程才能够获取锁。
正确的管理互斥锁异常复杂,这也是许多人之所以热衷于信道的原因。然而,在 Rust 中,得益于类型系统和所有权,我们不会在锁和解锁上出错。
Mutex<T>
是 std::sync
模块提供的一种同步原语,用于在多线程环境中保护共享数据。Mutex<T>
代表互斥锁(Mutex),它允许你以线程安全的方式对数据进行访问。以下是 Mutex<T>
的 API 详细介绍:
构造函数
Mutex::new(data: T)
: 创建一个新的Mutex
,并将data
作为内部数据。
方法
lock()
: 获取互斥锁,返回一个可变引用MutexGuard
。如果锁当前被其他线程持有,则当前线程将阻塞,直到锁被释放。try_lock()
: 尝试获取互斥锁,如果成功则返回Some(MutexGuard)
,如果失败(锁已被其他线程持有)则返回None
,不会阻塞当前线程。get_mut()
: 返回对内部可变数据的可变引用。这个方法需要Mutex
已经获得锁,通常在使用lock
或try_lock
之后使用。into_inner()
: 消耗Mutex
并返回内部数据。这个方法只有在Mutex
没有被锁定的情况下才能成功使用,否则会 panic。
实例方法
is_poisoned()
: 检查互斥锁是否处于“中毒”状态。如果一个线程在持有互斥锁时 panic,那么锁将进入中毒状态。其他线程在尝试获取这个锁时会收到一个PoisonError
。unlock()
: 释放互斥锁。通常,互斥锁会在MutexGuard
离开作用域时自动释放,但在某些情况下,你可能需要手动释放锁。
类型
MutexGuard<'a, T>
: 表示对互斥锁的锁定访问。它是通过调用lock
或try_lock
获得的。当MutexGuard
离开作用域时,互斥锁会自动释放。
错误处理
PoisonError<T>
: 当互斥锁中毒时,lock
和try_lock
方法会返回这个错误。它包含内部数据的一个可变引用,允许你安全地处理中毒情况。
当然,这里有一个使用 Mutex<T>
的 Rust 程序示例,它演示了如何在多个线程之间共享和修改数据:
use std::sync::{Arc, Mutex};
use std::thread;fn main() {// 创建一个Arc包装的Mutex,以便跨多个线程安全共享let counter = Arc::new(Mutex::new(0));let mut handles = vec![];// 创建并启动10个线程for _ in 0..10 {// 克隆Arc来获取counter的一个新引用let counter_clone = Arc::clone(&counter);let handle = thread::spawn(move || {// 通过lock获取互斥锁的访问权let mut num = counter_clone.lock().unwrap();// 修改数据*num += 1;});handles.push(handle);}// 等待所有线程完成for handle in handles {handle.join().unwrap();}// 打印最终的计数结果println!("Result: {}", *counter.lock().unwrap());
}
运行结果
Result: 10
这个程序执行以下步骤:
- 使用
Arc::new
和Mutex::new
创建一个在多个线程间共享的计数器。 - 创建一个线程向量
handles
来存储所有线程的句柄。 - 在一个循环中,为每个线程克隆
Arc
对象,以确保每个线程都有counter
的独立引用。 - 每个线程尝试获取
Mutex
的锁,递增计数器的值,然后释放锁。 - 使用
thread::spawn
启动每个线程。 - 在主线程中,等待所有子线程完成。
- 打印出最终的计数结果。
这个程序使用了 Arc
(Atomic Reference Counting) 来允许 Mutex
在多个线程间安全共享。Arc::clone
用于增加内部引用计数,而不是复制数据。每个线程通过调用 lock
方法来获取互斥锁的可变访问权,并通过 unwrap
处理可能的锁定错误(在实际应用中,你可能需要更优雅地处理这种错误)。当 MutexGuard
离开作用域时,互斥锁会自动释放。
四、使用 Sync 和 Send trait 的可扩展并发
之前讨论的几乎所有内容,都属于标准库,而不是语言本身的内容。由于不需要语言提供并发相关的基础设施,并发方案不受标准库或语言所限:我们可以编写自己的或使用别人编写的并发功能。
然而有两个并发概念是内嵌于语言中的:std::marker
中的 Sync
和 Send
trait。
4.1 通过 Send 允许在线程间转移所有权
Send
标记 trait 表明实现了 Send
的类型值的所有权可以在线程间传送。几乎所有的 Rust 类型都是 Send
的,不过有一些例外,包括 Rc<T>
:这是不能 Send
的,因为如果克隆了 Rc<T>
的值并尝试将克隆的所有权转移到另一个线程,这两个线程都可能同时更新引用计数。为此,Rc<T>
被实现为用于单线程场景,这时不需要为拥有线程安全的引用计数而付出性能代价。
因此,Rust 类型系统和 trait bound 确保永远也不会意外的将不安全的 Rc<T>
在线程间发送。而使用标记为 Send
的 Arc<T>
时,就没有问题了。
任何完全由 Send
的类型组成的类型也会自动被标记为 Send
。几乎所有基本类型都是 Send
的,除了裸指针(raw pointer)。
4.2 Sync 允许多线程访问
Sync
标记 trait 表明一个实现了 Sync
的类型可以安全的在多个线程中拥有其值的引用。换一种方式来说,对于任意类型 T
,如果 &T
(T
的不可变引用)是 Send
的话 T
就是 Sync
的,这意味着其引用就可以安全的发送到另一个线程。类似于 Send
的情况,基本类型是 Sync
的,完全由 Sync
的类型组成的类型也是 Sync
的。
智能指针 Rc<T>
也不是 Sync
的,出于其不是 Send
相同的原因。RefCell<T>
和 Cell<T>
系列类型不是 Sync
的。RefCell<T>
在运行时所进行的借用检查也不是线程安全的。Mutex<T>
是 Sync
的。
4.3 手动实现 Send 和 Sync 是不安全的
通常并不需要手动实现 Send
和 Sync
trait,因为由 Send
和 Sync
的类型组成的类型,自动就是 Send
和 Sync
的。因为它们是标记 trait,甚至都不需要实现任何方法。它们只是用来加强并发相关的不可变性的。
手动实现这些标记 trait 涉及到编写不安全的 Rust 代码,当前重要的是,在创建新的由不是 Send
和 Sync
的部分构成的并发类型时需要多加小心,以确保维持其安全保证。
以下是一个完整的示例,展示如何在一个多线程程序中手动实现 Send
和 Sync
,以及如何在多个线程之间共享数据。
首先,我们定义一个简单的结构体 SharedData
,它包含了一些数据,我们将为这个结构体手动实现 Send
和 Sync
特征。
use std::sync::{Arc, Mutex};
use std::thread;// 定义一个简单的结构体,包含一些数据
struct SharedData {count: i32,
}// 手动实现Send特征,表示SharedData可以安全地在线程间发送
unsafe impl Send for SharedData {}// 手动实现Sync特征,表示SharedData可以被多个线程安全地访问
unsafe impl Sync for SharedData {}fn main() {// 使用Arc来包装SharedData,使其可以被多个线程共享let shared_data = Arc::new(Mutex::new(SharedData { count: 0 }));// 创建多个线程,它们将共享并修改同一个SharedData实例let mut handles = vec![];for i in 0..10 {let data = Arc::clone(&shared_data);let handle = thread::spawn(move || {let mut data = data.lock().unwrap();println!("i: {}", i);data.count += i;});handles.push(handle);}// 等待所有线程完成for handle in handles {handle.join().unwrap();}// 打印最终的count值println!("Final count is: {}", shared_data.lock().unwrap().count);
}
运行结果
i: 1
i: 0
i: 2
i: 9
i: 8
i: 4
i: 3
i: 5
i: 7
i: 6
Final count is: 45
在这个例子中,我们首先定义了一个 SharedData
结构体,它包含一个 i32
类型的字段 count
。然后我们手动为 SharedData
实现了 Send
和 Sync
特征,使用 unsafe
关键字,因为我们需要确保 SharedData
在多线程环境中是安全的。
在 main
函数中,我们使用 Arc
(原子引用计数指针)和 Mutex
(互斥锁)来创建一个可以在多个线程之间安全共享的 SharedData
实例。Arc
允许多个线程拥有对数据的所有权,而 Mutex
确保在任何时刻只有一个线程可以修改数据。
我们创建了 10 个线程,每个线程都会增加 SharedData
中的 count
字段的值。由于我们为 SharedData
实现了 Send
和 Sync
,我们可以安全地将 Arc<Mutex<SharedData>>
的克隆传递给每个线程。每个线程完成其工作后,我们使用 join
方法等待所有线程完成,并打印最终的 count
值。
参考链接
- Rust 官方网站:https://www.rust-lang.org/zh-CN
- Rust 官方文档:https://doc.rust-lang.org/
- Rust Play:https://play.rust-lang.org/
- 《Rust 程序设计语言》
相关文章:
Rust 程序设计语言学习——并发编程
安全且高效地处理并发编程是 Rust 的另一个主要目标。并发编程(Concurrent programming),代表程序的不同部分相互独立地执行,而并行编程(parallel programming)代表程序不同部分同时执行,这两个…...
联邦学习研究综述【联邦学习】
文章目录 0 前言机器学习两大挑战: 1 什么是联邦学习?联邦学习的一次迭代过程如下:联邦学习技术具有以下几个特点: 2 联邦学习的算法原理目标函数本地目标函数联邦学习的迭代过程 3 联邦学习分类横向联邦学习纵向联邦学习联邦迁移…...
深入理解Python中的列表推导式
深入理解Python中的列表推导式 在Python编程中,列表推导式(List Comprehension)是一种简洁而强大的语法,用于创建和操作列表。它不仅提高了代码的可读性,还能显著减少代码的行数。本文将详细介绍什么是列表推导式,如何使用它,以及一些实际应用示例,帮助读者更好地理解…...
Android 实现左侧导航栏:NavigationView是什么?NavigationView和Navigation搭配使用
目录 1)左侧导航栏效果图 2)NavigationView是什么? 3)NavigationView和Navigation搭配使用 4)NavigationView的其他方法 一、实现左侧导航栏 由于Android这边没有直接提供左侧导航栏的控件,所以我尝试了…...
如何快速下载拼多多图片信息,效率高
图片是电商吸引顾客的关键因素,高质量的商品图片能提升产品吸引力,增强用户购买欲望。良好的视觉展示有助于建立品牌形象,提高转化率。同时,图片也是商品信息的主要传递媒介,对消费者决策过程至关重要。 使用图快下载器…...
windows 10下,修改ubuntu的密码
(1)在搜索框里面输入cmd,然后点击右键,选择管理员打开 Microsoft Windows [版本 10.0.22631.3880] (c) Microsoft Corporation。保留所有权利。 C:\Windows\System32>C: C:\Windows\System32>cd ../../ C:\>cd Users\ASUS\AppData\Local\Micros…...
【MySQL】慢sql优化全流程解析
定位慢sql 工具排查慢sql 调试工具:Arthas运维工具:Skywalking 通过以上工具可以看到哪个接口比较慢,并且可以分析SQL具体的执行时间,定位到哪个sql出了问题。 启用慢查询日志 慢查询日志记录了所有执行时间超过指定参数(lon…...
RabbitMQ高级特性 - 消息分发(限流、负载均衡)
文章目录 RabbitMQ 消息分发概述如何实现消费分发机制(限制每个队列消息数量)使用场景限流背景实现 demo 非公平发送(负载均衡)背景实现 demo RabbitMQ 消息分发 概述 RabbitMQ 的队列在有多个消费者订阅时,默认会通过…...
信号处理——自相关和互相关分析
1.概括 在信号处理中,自相关和互相关是相关分析非常重要的概念,它们能分析一个信号或两个信号在时间维度的相似性,在振动测试分析、雷达测距和声发射探伤得到了广泛的应用。自相关分析的研究对象为一个信号,互相关分析的研究对象…...
如何解决部分设备分辨率不适配
1)如何解决部分设备分辨率不适配 2)Unity中如何实现草的LOD 3)使用了Play Asset Delivery提交版本被Google报错 4)如何计算弧线弹道的落地位置 这是第396篇UWA技术知识分享的推送,精选了UWA社区的热门话题,…...
C#插件 调用存储过程(输出参数类型)
存储过程 CREATE PROCEDURE [dbo].[GetSum]num1 INT,num2 INT,result INT OUTPUT AS BEGINselect result num1 num2 END C#代码 using Kingdee.BOS; using Kingdee.BOS.App.Data; using Kingdee.BOS.Core.Bill.PlugIn; using Kingdee.BOS.Util; using System; using System.…...
代码随想录算法训练营day32 | 509. 斐波那契数 、70. 爬楼梯 、746. 使用最小花费爬楼梯
碎碎念:开始动态规划了!加油! 参考:代码随想录 动态规划理论基础 动态规划常见类型: 动规基础类题目背包问题打家劫舍股票问题子序列问题 解决动态规划问题应该要思考清楚的: 动态规划五部曲࿱…...
【人工智能专栏】Learning Rate Decay 学习率衰减
Learning Rate Decay 学习率衰减 使用格式 optimizer = torch.optim.SGD(model.paraters(), lr=0.1, momentum=0.9, weight_decay=1e-4) scheduler = torch.optim...
浙大版《C语言程序设计(第3版)》题目集
练习4-11 统计素数并求和 本题要求统计给定整数M和N区间内素数的个数并对它们求和。 输入格式: 输入在一行中给出两个正整数M和N(1≤M≤N≤500)。 输出格式: 在一行中顺序输出M和N区间内素数的个数以及它们的和,数字间以空格分隔。 输入…...
【学习笔记】Day 2
一、进度概述 1、inversionnet_train_light 试运行——未成功 2、DL-FWI基础入门培训-1,2,以及作业1的完成——暂未完成作业 二、详情 1、inversionnet_train_light 试运行 在补充完相关依赖后,运行仍有报错 产生原因:这个代码在当…...
Java中的Map(如果想知道Java中有关Map的知识点,那么只看这一篇就足够了!)
前言:在Java编程语言中,集合框架(Collection Framework)提供了一系列用于存储和操作数据的接口和类。其中,Map和Set是两个非常重要的接口,分别用于存储键值对和无重复元素的集合。 ✨✨✨这里是秋刀鱼不做梦…...
裸金属服务器详解
在云计算飞速发展的今天,裸金属服务器(Bare Metal Server, BMS)作为一种兼具传统物理服务器性能和虚拟化服务优势的计算资源,正逐渐成为企业和个人用户的重要选择。今天我们就来了解下关于裸金属服务器的定义、核心特点以及其在各…...
等待唤醒机制两种实现方法-阻塞队列
桌子上有面条-》吃货执行 桌子上没面条-》生产者制造执行 1、消费者等待 消费者先抢到CPU执行权,发现桌子上没有面条,于是变成等待wait状态,并释放CPU执行权,此时的CPU肯定会被厨师抢到,初始开始做面条,…...
数组项相加和 – 如何将 JavaScript 数组中的数字相加
JavaScript 中的数组是一个对象,它允许您在单个变量名称下存储多个值的有序集合,并以多种方式操作这些值。 在本文中,您将学习如何使用几种不同的方法计算给定数组中所有数字的总和。 具体来说,使用以下方法得到数组中所有数字的总…...
C#和S7-1200PLC S7.NET通信
1、一步步建立一个C#项目 一步步建立一个C#项目(连续读取S7-1200PLC数据)_s7协议批量读取-CSDN博客文章浏览阅读1.7k次,点赞2次,收藏4次。这篇博客作为C#的基础系列,和大家分享如何一步步建立一个C#项目完成对S7-1200PLC数据的连续读取。首先创建一个窗体应用。_s7协议批量…...
常用命令git branch
Git Branch 命令总结 列出分支 git branch:显示本地分支,当前分支会被标记。git branch -r:显示远程分支。git branch -a:显示所有本地和远程分支。 创建分支 git branch <branch_name>:创建一个新分支但不自…...
Android 制作系统签名
一、切换目录 cd build/target/product/security二、执行命令 1)将使用.pk8生成platform.priv.pem (.pem即可,文件名可随意修改)openssl pkcs8 -in platform.pk8 -inform DER -outform PEM -out platform.pem -nocrypt2)生成.p12,此时需输入两次密码,并且要记住 -name后所设置…...
C语言第13篇
1.下面程序是计算n个数的平均值,请填空.______ #include<stdio.h> void main( ) { int i,n; float x,avg0.0; scanf("%d",&n); for(i0;i<n;i) { scanf("%f",&x); avgavg______; } avg________; printf("avg%f\n",avg); } A) …...
基于FPGA的数字信号处理(22)--进位保存加法器(Carry Save Adder, CSA)
目录 1、拆解多个数的加法 2、进位保存加法器 3、CSA的优点和缺点 4、CSA电路的实现 文章总目录点这里:《基于FPGA的数字信号处理》专栏的导航与说明 1、拆解多个数的加法 考虑3个4bits数相加,10 4 7 21 的过程是这样的: 其中的红色数…...
idea使用free流程,2024idea、2023idea都可以安装免费使用
1.先到官网下载,这里选择win系统的,点击下图的.exe https://www.jetbrains.com/idea/download/?sectionwindows 2.下载好后基本上就是一直点击“下一步”到直到安装好,安装好后先打开软件后关闭退出 3.下载配配套资料 链接: https://pan.ba…...
设计模式 之 —— 抽象工厂模式
目录 什么是抽象工厂模式? 定义 特点 抽象工厂模式(java代码示例) 首先定义第一个接口 实现第一个接口的类 定义第二个接口 实现第二个接口的类 * 创建抽象工厂类 创建扩展了 AbstractFactory 的工厂类 饮料工厂 食物工厂 * 创建一个…...
计量经济学(十六)--一文读懂和学会医学统计学中的四种检验方法
1. 统计学是什么? 统计学是应用数学的一个分支,主要通过利用概率论建立数学模型,收集所观察系统的数据,进行量化的分析、总结,并进而进行推断和预测,为相关决策提供依据和参考。它被广泛的应用在各门学科之上,从物理和社会科学到人文科学,甚至被用来工商业及政府的情报…...
解析 C# Dictionary 代码
entries用于存储当前每个节点的数据,其中四个字段分别表示: hashCode:key对应的hash值next:处理hash冲突,可以理解为是一个链表结构,邻接表key:存储的keyvalue:存储的value bucket…...
如何利用人工智能提升工作效率
在当今这个信息爆炸的时代,我们每天都被大量的工作任务所困扰。然而,随着人工智能技术的不断发展,我们可以通过一些智能工具来提升我们的工作效率。在这篇文章中,我将分享一些关于如何利用人工智能提升工作效率的建议。 首先&…...
Linux驱动开发—Linux内核定时器概念和使用详解,实现基于定时器的字符驱动
文章目录 内核定时器概念在Linux驱动模块中使用定时器软定时器(Soft Timers)jiffies 含义高精度定时器(High Resolution Timers) 实现倒计时字符设备驱动 内核定时器概念 在 Linux 内核中,定时器是用来管理和调度延迟…...
mysql数据库:数据库,表和列的基本概念
mysql:数据库,表和列的基本概念以及导入和导出文件 数据库的概念和用途 数据库是一个有组织的数据集合,它们被存储在计算机上以便于管理和访问。数据库的主要目的是为了存储和管理数据,同时使数据能够被高效地访问、检索和更新。数…...
Nextjs 使用 graphql,并且接入多个节点
写在前面 随着区块链技术的流行,也促进了 subgraph 工具的兴起。那么如何在前端接入 graphql 节点就成了关键,其接入方式既存在与 restful 接口相类似的方式,也有其独特接入风格。本文将介绍如何接入 graphql 以及如何应对多个 graphql 节点…...
小结——知识注入
所谓知识注入,其实不该脱离于LLM的基础工作原理,然后空谈抽象概念。 知识,也就是你问他问题,他能输出正确的回答,这只是一个简单的输出token的过程。输出得准了,就是知识,输出不准了,…...
科普文:微服务之Spring Cloud Alibaba组件Nacos一致性协议Distro+Raft概叙
一、概要 Nacos是阿里开放的一款中间件,它主要提供三种功能:持久化节点注册,非持久化节点注册和配置管理。 二、一致性协议 - AP/CP Nacos不是纯粹的AP服务,也不是纯粹的CP服务,而是两者同时支持。 这要从服务注册…...
python合并音视频-通过ffmpeg合并音视频
🌈所属专栏:【python】✨作者主页: Mr.Zwq✔️个人简介:一个正在努力学技术的Python领域创作者,擅长爬虫,逆向,全栈方向,专注基础和实战分享,欢迎咨询! 您的…...
Yolov8添加ConvNetV1和V2模块
Yolov8添加ConvNet模块 1 ConvNet系列相关内容 (1)2022 论文地址:A ConvNet for the 2020s Code Link 如下图所示,精度、效率、尺寸都很不错。 论文的摘要如下: 视觉识别的“咆哮的 20 年代”始于视觉注意力 &…...
十个常见的 Python 脚本 (详细介绍 + 代码举例)
1. 批量重命名文件 介绍: 该脚本用于批量重命名指定目录下的文件,例如将所有 ".txt" 文件重命名为 ".md" 文件。 import osdef batch_rename(directory, old_ext, new_ext):"""批量重命名文件扩展名。Args:directory: 要处理…...
【C语言】详解feof函数和ferror函数
文章目录 前言1. feof1.1 feof函数原型1.2 正确利用函数特性读写文件1.2.1 针对文本文件1.2.2 针对二进制文件 1.3 feof函数的原理1.4 feof函数实例演示 2. ferror2.1 ferror函数原型 前言 或许我们曾在网络上看过有关于feof函数,都说这个函数是检查文件是否已经读…...
ValueListenableBuilder 和 addListener 在 ChangeNotifier的区别
1、前言 ValueListenableBuilder 和 addListener 在 ChangeNotifier 中有不同的用途和用法,适用于不同的场景。它们的主要区别在于它们如何监听和响应状态变化,以及它们的用法和特性。 2、ValueListenableBuilder用法 ValueListenableBuilder 是一个 …...
ScriptEcho:AI赋能的前端代码生成神器
ScriptEcho:AI赋能的前端代码生成神器 在前端开发中,如果你总是觉得写代码太费时费力,那么 ScriptEcho 将成为你的救星。这个 AI 代码生成平台不仅能帮你省下大量时间,还能让你轻松愉快地写出生产级代码。本文将带你了解 ScriptEc…...
TypeError: ‘float’ object is not iterable 深度解析
TypeError: ‘float’ object is not iterable 深度解析与实战指南 在Python编程中,TypeError: float object is not iterable是一个常见的错误,通常发生在尝试对浮点数(float)进行迭代操作时。这个错误表明代码中存在类型使用不…...
灵茶八题 - 子序列 +w+
灵茶八题 - 子序列 w 题目描述 给你一个长为 n n n 的数组 a a a,输出它的所有非空子序列的元素和的元素和。 例如 a [ 1 , 2 , 3 ] a[1,2,3] a[1,2,3] 有七个非空子序列 [ 1 ] , [ 2 ] , [ 3 ] , [ 1 , 2 ] , [ 1 , 3 ] , [ 2 , 3 ] , [ 1 , 2 , 3 ] [1],[…...
为什么美元债务会越来越多?
美元债务规模持续膨胀,其背后原因复杂多样,可归结为以下几个主要因素: 财政赤字和刺激政策是导致美元债务增加的重要原因。美国政府长期面临财政赤字问题,支出远超收入,为弥补这一缺口,政府不得不大量发行…...
二维凸包算法 Julia实现
问题描述:给定平面上 n n n 个点的集合 Q Q Q,求其子集 P P P 构成 Q Q Q 的凸包,即 ∀ p ∈ Q , ∃ p 0 , p 1 , p 2 ∈ P \forall p \in Q, \exist p_0, p_1, p_2 \in P ∀p∈Q,∃p0,p1,p2∈P 使得点 p p p 在以点 p 0 , p 1 …...
python dash框架
Dash 是一个用于创建数据分析型 web 应用的 Python 框架。它由 Plotly 团队开发,并且可以用来构建交互式的 web 应用程序,这些应用能够包含图表、表格、地图等多种数据可视化组件。 Dash 的特点: 易于使用:Dash 使用 Python 语法…...
2.外部中断(EXTI)
理论 NVIC:嵌套向量中断控制器(解释教程) 外部通用中断线(EXTI0~EXTI15):每个GPIO设置成中断模式,与中断控制器连接的线 外部中断触发方式 上升沿触发、下降沿触发、双边沿触发 外部中断触发函数 在stm32f1xx_it.c文件…...
Python | SyntaxError: invalid syntax 深度解析
Python | SyntaxError: invalid syntax 深度解析 在Python编程中,SyntaxError: invalid syntax是一个常见的错误,它表明Python解释器在尝试解析代码时遇到了语法问题。这个错误通常是由于代码中存在拼写错误、缺少符号(如括号、冒号或逗号&a…...
付费进群系统源码原版最新修复全开源版
付费进群,和平时所见到的别人拉你进群是不一样的,付费进群需要先缴费以后,才会看到群的二维码,扫码进群或者是长按二维码图片识别进群,付费进群这个功能广泛应用于拼多多的砍价群,活动的助力群,…...
Docker容器部署的SpringBoot项目jar包,上传文件但是找不到路径的问题
在docker容器内部署的jar包运行后,请求访问都没有问题,在文件上传时,发现上传图片接口响应成功,但是图片路径报404错误,发现找不到路径。 在服务器上查看也没有找到相关图片。 原因: 启动docker镜像时没…...
云计算学习——5G网络技术
系列文章目录 提示:仅用于个人学习,进行查漏补缺使用。 Day1 网络参考模型 Day2 网络综合布线与应用 Day3 IP地址 Day4 华为eNSP网络设备模拟器的基础安装及简单使用 Day5 交换机的基本原理与配置 Day6 路由器的原理与配置 Day7 网络层协议介绍一 Day8 传…...