范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

从单线程小菜鸟到可以轻松地应对Rust并发编程,让CPU为我鼓掌!

  在现代计算机中,多核处理器已经非常常见。这也就意味着,我们需要编写能够利用多核处理器的并发程序,以充分利用计算机的资源。Rust 提供了一些机制来实现并发编程,其中最重要的是多线程。
  Rust 的多线程机制与其他语言的多线程机制不同,它采用了一种称为"所有权模型"的内存管理模型,以保证并发程序的安全性和正确性。这种模型使得多线程编程更容易和更安全,而不会出现内存泄漏和数据竞争等问题。
  接下来我们将继续深入学习 Rust 并发编程的基础知识和高级主题,以及 Rust 并发编程的实践经验和最佳实践。Rust 的并发编程是其最重要的特性之一,因为它支持多线程和异步编程,能够提高程序的性能和响应能力。在这个学习笔记中,我们将介绍 Rust 的并发模型,线程和共享状态的基础知识,以及 Rust 的锁、互斥体、原子类型、通道和消息传递等机制。我们还将探讨 Rust 中的异步编程和 Tokio 框架、高级同步构造和性能优化等主题,同时介绍 Rust 并发编程的实践案例和项目实战,以及 Rust 并发编程的最佳实践。本学习笔记将帮助您掌握 Rust 并发编程的核心概念和实践经验,使您能够在实际开发中应用 Rust 的并发编程技术来构建高效和可靠的软件系统。
  Rust 并发编程基础
  1.Rust 并发模型
  Rust 并发模型是一种基于线程和消息传递的并发编程模型,它允许多个线程同时执行任务,并通过消息传递进行通信和同步。
  在 Rust 并发模型中,每个线程都是独立的执行单元,线程之间没有共享的状态,这避免了线程间的竞争条件和数据竞争问题。相反,线程之间通过消息传递进行通信,每个线程都可以独立地处理消息。
  Rust 的并发模型可以用于多种场景,例如网络编程、图形界面编程、并行计算和多核处理等。
  举个例子,假设我们有一个 web 服务器,它需要同时处理多个客户端请求。在传统的并发编程模型中,我们可能会使用多线程或进程来实现,但是这会带来线程竞争和数据竞争等问题。而在 Rust 的并发模型中,我们可以创建多个独立的线程,每个线程负责处理一个客户端请求,线程之间通过消息传递进行通信和同步,从而避免了线程竞争和数据竞争问题。这种并发模型可以保证服务器的性能和稳定性,同时简化了编程模型。
  2.线程基础
  在 Rust 中,线程是基本的并发构建块之一,它允许我们同时执行多个任务。Rust 提供了创建和管理线程的标准库,使得在多线程环境下编写代码变得相对简单。
  在 Rust 中,线程可以通过 std::thread 模块来创建。以下是一个创建线程并运行简单任务的示例代码:use std::thread;  fn main() {     let handle = thread::spawn(|| {         println!("Hello from a thread!");     });      handle.join().unwrap(); }
  在这个例子中,我们使用 thread::spawn 函数创建一个新的线程,并将其与一个闭包绑定在一起。该闭包实现了线程要执行的代码逻辑。在 thread::spawn 调用后,我们会得到一个 JoinHandle 类型的返回值,它可以用于等待线程完成并获取其返回值。在这里,我们使用 handle.join() 来等待线程完成,以确保打印语句完成执行。
  在 Rust 中,线程的创建和管理有许多细节需要注意,例如线程的安全性、线程间的通信、错误处理等。我们需要仔细地设计和编写代码,以避免潜在的问题。
  3.共享状态与可变性
  在 Rust 并发编程中,共享状态和可变性是非常重要的概念,因为它们涉及到多个线程之间的数据交互问题。
  在单线程环境下,我们可以轻松地实现对变量的读取和写入,但在多线程环境下,由于多个线程可以同时访问和修改同一个变量,因此可能会导致数据竞争(Data Race)的问题。
  为了解决这个问题,Rust 提供了一些机制来保证共享状态的安全性,其中最基本的机制是通过在编译时检查来防止数据竞争。
  具体来说,Rust 的共享状态和可变性控制机制主要包括以下几个方面:所有权和借用机制:Rust 通过所有权和借用机制来确保在任何时刻,只有一个线程拥有对一个特定变量的所有权,并且只有在这个所有权被释放之后,其他线程才能访问这个变量。Sync 和 Send 特性:Rust 为多线程编程提供了一些特殊的 trait,如 Sync 和 Send。其中,Sync 表示类型是线程安全的,Send 表示类型可以在线程之间传递。内存模型:Rust 的内存模型采用了严格的线程安全规则,通过限制线程访问共享内存的方式来避免数据竞争问题。同时,Rust 还提供了一些原子类型和操作,如原子引用计数和原子计数器,以帮助开发者实现高效的并发编程。
  在实际编程中,开发者需要根据具体的需求来选择适合的共享状态和可变性控制方式。例如,对于一些简单的共享状态,可以使用 Rust 的原子类型和操作来保证线程安全性;对于更复杂的数据结构,可能需要使用锁或通道等高级同步构造来控制访问和修改权限。
  4.锁和互斥体
  在Rust中,锁和互斥体是处理共享状态的主要工具之一。锁是一种同步原语,它可以防止同时访问共享资源。当一个线程想要访问共享资源时,它必须先获得锁。如果锁已经被另一个线程持有,那么线程就会被阻塞,直到锁被释放。
  Rust中的互斥体是一种特殊的锁,它提供了对共享资源的独占访问。当一个线程获取了互斥体的所有权后,其他线程就不能再访问共享资源了,直到互斥体被释放。
  以下是一个使用互斥体保护共享计数器的例子:use std::sync::Mutex;  fn main() {     let counter = Mutex::new(0);      let mut handles = vec![];      for _ in 0..10 {         let handle = std::thread::spawn({             let counter = counter.clone();             move || {                 let mut num = counter.lock().unwrap();                  *num += 1;             }         });          handles.push(handle);     }      for handle in handles {         handle.join().unwrap();     }      println!("Result: {}", *counter.lock().unwrap()); }
  在这个例子中,我们使用了一个互斥体来保护计数器。在每个线程中,我们先克隆了互斥体的所有权,并调用lock()方法来获取计数器的可变引用。由于互斥体提供了独占访问,所以只有一个线程可以持有计数器的可变引用。其他线程必须等待锁被释放后才能访问计数器。在线程结束后,我们使用join()方法等待所有线程执行完毕,并打印出计数器的最终结果。
  需要注意的是,在使用互斥体时,一定要小心死锁的情况。死锁指的是多个线程互相等待对方释放锁,导致所有线程都被阻塞的情况。在使用互斥体时,我们应该尽量避免嵌套锁,以免死锁的发生。
  5.Atomics 和 Memory Ordering
  在Rust中,原子操作(atomic operations)可以用来实现并发访问共享数据的同步和互斥。原子操作指的是无法被中断的操作,即不会被其他线程干扰,保证了操作的原子性。Rust提供了一系列的原子类型,如AtomicBool、AtomicUsize等,这些类型都实现了一些原子方法,如fetch_add、fetch_sub等。这些方法可以保证操作的原子性,同时也保证了线程安全。
  Memory Ordering则是指Rust在进行原子操作时,保证操作顺序和可见性的机制。在Rust中,原子操作默认使用SeqCst(Sequential Consistent)的内存顺序,即顺序一致性。顺序一致性是指多线程执行时,所有线程看到的操作顺序都是一致的,这种机制可以保证操作的正确性,但可能会影响性能。
  此外,Rust也提供了其他的内存顺序,如Acquire和Release,可以在保证操作正确性的前提下提高性能。
  举例来说,当一个线程对一个原子计数器进行fetch_add操作时,Rust会保证该操作的原子性,并根据所选的内存顺序来保证该操作与其他操作的执行顺序。例如:use std::sync::atomic::{AtomicUsize, Ordering};  let counter = AtomicUsize::new(0);  // 线程1 counter.fetch_add(1, Ordering::SeqCst);  // 线程2 counter.fetch_add(1, Ordering::SeqCst);
  这里创建了一个AtomicUsize类型的计数器counter,并在两个线程中分别对它进行fetch_add操作。Rust会保证这些操作的原子性,并根据SeqCst内存顺序来保证操作的正确性。
  6.通道(channel)和消息传递
  在 Rust 中,通道(channel)是一种实现并发消息传递的机制,它允许多个线程通过发送和接收消息来进行通信和同步。通道分为两种类型:单向通道和双向通道。单向通道只能用于一种方向的通信,而双向通道则可以在两个方向上发送和接收消息。
  在 Rust 中,可以使用标准库中的mpsc模块来实现通道。mpsc模块中的channel函数可以创建一个新的通道,它返回一个发送器(sender)和一个接收器(receiver),这两个对象可以在不同的线程之间传递。通常情况下,通道的发送端和接收端是在不同的线程中创建的,它们通过通道来传递消息和同步。
  以下是一个简单的示例代码,展示了如何在 Rust 中使用通道进行消息传递和同步:use std::sync::mpsc; use std::thread;  fn main() {     let (tx, rx) = mpsc::channel();      let handle = thread::spawn(move || {         let data = rx.recv().unwrap();         println!("Received data: {}", data);     });      let data = "hello world".to_string();     tx.send(data).unwrap();      handle.join().unwrap(); }
  在这个示例中,我们首先调用了mpsc::channel()函数创建了一个通道,它返回一个发送器tx和一个接收器rx。然后,我们在新线程中使用rx.recv()方法来接收发送端发送的消息,并将消息打印出来。接着,我们在主线程中使用tx.send()方法向发送端发送了一条消息。最后,我们调用了handle.join()方法等待新线程执行完毕。
  通过通道,我们可以实现线程之间的协调和同步,从而避免了数据竞争和死锁等问题,提高了程序的可靠性和性能。Rust 并发编程高级主题
  1.生命周期和并发
  在 Rust 并发编程中,理解生命周期是非常重要的。生命周期可以用来描述变量引用的有效范围,对于并发程序来说,生命周期可以用来保证线程安全和避免数据竞争。
  Rust 的 borrow checker 确保在编译时就能检测出数据竞争的情况,因此可以帮助开发人员避免许多并发编程中的错误。在并发编程中,生命周期的正确使用可以保证多个线程之间共享数据时不会出现竞争和数据不一致的情况。
  同时,Rust 也提供了一些机制来处理并发编程中的生命周期问题,例如 Arc 和 Rc 用于共享拥有所有权的值,以及 MutexGuard 和 RefCell 用于在运行时跟踪借用规则。
  需要注意的是,在并发编程中使用生命周期时需要非常小心,否则可能会引入新的问题。因此,在学习并发编程的过程中,需要仔细研究 Rust 的生命周期规则,并在实践中多加练习。
  一个例子是,假设有一个 Vec 向量,并发地添加元素。在这种情况下,需要使用 Arc 和 Mutex 来确保线程安全。示例代码如下:use std::sync::{Arc, Mutex}; use std::thread;  fn main() {     let vec = Arc::new(Mutex::new(vec![]));      let mut threads = vec![];     for i in 0..10 {         let vec = vec.clone();         let thread = thread::spawn(move || {             vec.lock().unwrap().push(i);         });         threads.push(thread);     }      for thread in threads {         thread.join().unwrap();     }      println!("{:?}", vec.lock().unwrap()); }
  在这个例子中,Arc 用于引用计数和共享所有权,Mutex 用于确保在访问 vec 时只有一个线程能够访问。通过在并发代码中使用生命周期和 Rust 提供的线程安全机制,可以安全地并发地修改向量。
  2.异步编程和 Futures
  异步编程是一种基于事件驱动的编程模型,它通过避免阻塞线程来提高程序的性能和并发性。在 Rust 中,异步编程需要使用 Futures 和 async/await 语法。
  Future 是一个表示某个计算的结果的占位符,它可以让程序在等待某个 I/O 操作完成时不被阻塞。在 Rust 中,Future 是一个 trait,它定义了一系列方法,比如 poll 和 then,这些方法允许异步任务在后台进行处理。
  async/await 语法是 Rust 中用于异步编程的主要语法。它可以让开发者以同步的方式编写异步代码。通过 async/await,开发者可以将异步操作转换为类似于同步代码的样子,这使得异步代码更加易读易写。
  除了 Futures 和 async/await 语法之外,Rust 还提供了一些异步编程相关的库和工具,比如 Tokio 和 async-std。这些库可以帮助开发者更方便地进行异步编程,并提供了许多基于 Future 的异步 API 和工具。
  3.异步编程和 Tokio 框架
  以下是一个简单的 Tokio demo,用于说明 Tokio 的异步编程特性。
  首先,在 Cargo.toml 文件中添加 tokio 的依赖:[dependencies] tokio = { version = "1.15.0", features = ["full"] }
  接下来,创建一个异步函数 async_fetch_url,用于使用 Tokio 的异步 I/O 功能获取 URL 的响应内容:use std::error::Error;  #[tokio::main] async fn main() -> Result<(), Box> {     let url = "https://www.example.com";     let response = async_fetch_url(url).await?;     println!("{}", response);     Ok(()) }  async fn async_fetch_url(url: &str) -> Result> {     let response = reqwest::get(url).await?.text().await?;     Ok(response) }
  在这个例子中,我们使用了 tokio::main 宏,它会为我们创建一个异步运行时并将 main 函数转换为一个异步函数。
  在 async_fetch_url 函数中,我们使用了 reqwest 库获取 URL 的响应内容,并通过 async 和 await 关键字创建了异步执行的上下文。
  最后,在 main 函数中,我们调用了 async_fetch_url 函数并打印了获取到的响应内容。
  这个简单的例子展示了 Tokio 的异步编程特性,它可以让我们在 Rust 中使用高效的异步 I/O 操作,避免了阻塞和等待。
  4.并发编程中的性能优化
  在 Rust 并发编程中,性能优化非常重要,特别是对于高性能的应用程序。下面是一些常用的性能优化技巧:减少锁的使用:锁是在多线程程序中非常重要的同步工具,但是使用过多锁会降低程序的性能。因此,可以考虑减少锁的使用,例如使用无锁的数据结构,或者使用锁的粒度更细的数据结构。减少线程的创建和销毁:线程的创建和销毁需要消耗很多的系统资源,因此可以考虑使用线程池,重用已有的线程,避免不必要的创建和销毁。使用消息传递而非共享内存:消息传递是一种更轻量级的同步方式,比共享内存更容易实现和维护。在 Rust 中,可以使用通道(channel)来进行消息传递。避免不必要的内存分配和复制:在并发编程中,内存分配和复制是非常耗费性能的操作。因此可以考虑使用对象池、缓存等技术来减少不必要的内存分配和复制。避免过度同步:过度同步会降低程序的并发度,导致性能瓶颈。因此可以考虑采用更细粒度的同步方式,例如锁粒度更小的锁,或者采用更轻量级的同步方式,例如原子操作、读写锁等。
  具体的优化方式需要根据具体的应用场景来确定。在编写并发程序时,需要不断地测试和优化,才能获得更好的性能和并发度。
  5.错误处理和并发编程
  在 Rust 并发编程中,错误处理是非常重要的一部分,特别是在多线程、异步编程等场景中,错误处理往往会更加复杂和困难。
  Rust 提供了一些处理错误的机制,比如 Result 和 Option 等枚举类型,还有 panic! 宏用于在遇到不可恢复的错误时抛出异常。
  在并发编程中,需要考虑的错误类型也比较多,例如竞争条件(race condition)、死锁(deadlock)、饥饿(starvation)等。
  举个例子,假设我们有一个计数器(Counter)的结构体,多个线程会对其进行增加操作(increment),但是如果没有处理好竞争条件,就可能导致计数器的值出现错误的情况。use std::sync::atomic::{AtomicUsize, Ordering};  struct Counter {     value: AtomicUsize, }  impl Counter {     fn increment(&self) {         self.value.fetch_add(1, Ordering::SeqCst);     } }  fn main() {     let counter = Counter { value: AtomicUsize::new(0) };     let mut handles = vec![];      for _ in 0..10 {         let handle = std::thread::spawn(|| {             for _ in 0..10000 {                 counter.increment();             }         });         handles.push(handle);     }      for handle in handles {         handle.join().unwrap();     }      println!("Counter value: {}", counter.value.load(Ordering::SeqCst)); }
  在这个例子中,我们定义了一个 Counter 结构体,其中包含了一个 AtomicUsize 类型的 value 字段,用于存储计数器的值,并在其上实现了一个 increment 方法用于增加计数器的值。同时,我们使用了多线程的方式对该计数器进行了并发地增加操作,最终输出计数器的值。
  需要注意的是,由于 value 字段是一个原子类型,在并发场景下,我们需要使用正确的内存模型来确保正确性。在这个例子中,我们使用了 Ordering::SeqCst 内存模型,它是最保守和最常用的内存模型。
  如果我们不使用原子类型,而是使用普通的整型变量,那么在多线程并发访问时,就很容易出现竞争条件的情况。这种情况下,程序运行的结果是不确定的,并且会出现错误的计数器值。而使用原子类型,就可以保证在并发场景下仍然能够正确地增加计数器的值。
  6.原子引用计数和并发计数器
  原子引用计数是 Rust 中的一种线程安全的引用计数(Reference Counting)方式。与传统的引用计数不同,原子引用计数在并发场景下可以保证安全性,避免了数据竞争。在 Rust 标准库中,可以通过 std::sync::Arc 来使用原子引用计数。
  下面是一个简单的示例代码,演示了如何使用原子引用计数。在示例代码中,我们使用一个 Arc 对象 data 来同时让多个线程访问同一个 Vec 对象。use std::sync::Arc; use std::thread;  fn main() {     let data = Arc::new(vec![1, 2, 3, 4, 5]);      for i in 0..5 {         let data_ref = data.clone();         thread::spawn(move || {             let sum: i32 = data_ref.iter().sum();             println!("Thread {} sum: {}", i, sum);         });     } }
  在上面的示例中,我们使用了 Arc::new 来创建了一个包含整数序列的 Vec 对象,并使用 Arc::clone 来创建了多个 Arc 对象 data_ref,让多个线程共享同一个 Vec 对象。在每个线程中,我们使用 data_ref.iter().sum() 来计算 Vec 中所有元素的和,并输出结果。通过运行程序,我们可以看到每个线程都成功地计算出了元素的和,并输出了正确的结果。
  原子计数器(Atomic Counter)则是一种常用的原子类型,它可以在并发环境下进行安全的计数操作。在 Rust 标准库中,可以使用 std::sync::atomic::AtomicUsize 来创建一个原子计数器。
  下面是一个简单的示例代码,演示了如何使用原子计数器。在示例代码中,我们使用 AtomicUsize 来实现了一个计数器 counter,并让多个线程同时对其进行递增操作。use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread;  fn main() {     let counter = AtomicUsize::new(0);      for i in 0..5 {         let counter_ref = counter.clone();         thread::spawn(move || {             for j in 0..1000 {                 counter_ref.fetch_add(1, Ordering::Relaxed);             }             println!("Thread {} finished", i);         });     }      // 等待所有线程执行完毕     thread::sleep_ms(1000);      // 输出计数器的值     println!("Counter: {}", counter.load(Ordering::Relaxed)); }
  在上面的示例中,我们使用了 AtomicUsize::new 来创建了一个原子计数器,并使用 fetch_add 来对计数器进行递增操作。在每个线程中,我们使用了 for 循环来进行递增操作,最终输出线程执行完毕的提示。
  7.高级同步构造,如Barrier、Condvar和Semaphore
  在 Rust 并发编程中,有一些高级的同步构造可以用来更加灵活地控制线程之间的通信和同步。下面我们简单介绍一下其中三种:Barrier、Condvar 和 Semaphore。
  7.1. Barrier
  Barrier 是一个同步原语,它可以让多个线程在一个点上等待,直到所有线程都到达该点才能继续执行。Barrier 主要有两个方法:wait 和 wait_timeout。
  wait 方法会使当前线程等待其他线程到达 Barrier,直到所有线程都到达 Barrier,所有线程才会同时解除等待状态,继续往下执行。
  wait_timeout 方法也是等待其他线程到达 Barrier,但是可以设置一个超时时间,如果等待超时,当前线程就会继续执行。
  下面是一个简单的示例,演示了如何使用 Barrier:use std::sync::{Arc, Barrier}; use std::thread;  fn main() {     let mut threads = vec![];     let barrier = Arc::new(Barrier::new(4));      for i in 0..4 {         let c = barrier.clone();         let t = thread::spawn(move || {             println!("thread {} before wait", i);             c.wait();             println!("thread {} after wait", i);         });         threads.push(t);     }      for t in threads {         t.join().unwrap();     } }
  上面的代码中,我们首先创建了一个 Barrier 对象,并将它的引用保存到了 Arc 中。然后,我们创建了四个线程,每个线程都会先打印 "thread x before wait",然后等待其他线程到达 Barrier,最后打印 "thread x after wait"。
  在主线程中,我们调用了每个线程的 join 方法,以等待它们全部执行完毕。
  7.2.Condvar
  Condvar 是另一个同步原语,它提供了一种机制,可以让线程在某个条件变量满足时阻塞等待,而不是简单地一直轮询。Condvar 主要有三个方法:wait、wait_timeout 和 notify_one。
  wait 方法会使当前线程等待条件变量满足,直到另外一个线程调用了 Condvar 的 notify_one 方法来唤醒它。
  wait_timeout 方法也是等待条件变量满足,但是可以设置一个超时时间,如果等待超时,当前线程就会继续执行。
  notify_one 方法会唤醒一个在等待条件变量的线程。
  下面是一个简单的示例,演示了如何使用 Condvar:use std::sync::{Arc, Mutex, Condvar}; use std::thread;  fn main() {     let pair = Arc::new((Mutex::new(false), Condvar::new()));     let pair2 = pair.clone();      let handle = thread::spawn(move || {         let &(ref lock, ref cvar) = &*pair2;         let mut started = lock.lock().unwrap();         *started = true;         cvar.notify_one();         // Do some work     });      let &(ref lock, ref cvar) = &*pair;     let mut started = lock.lock().unwrap();     while !*started {         started = cvar.wait(started).unwrap();     }     // Do some other work     handle.join().unwrap(); }
  这个示例创建了一个互斥锁和条件变量的元组,并使用 Arc 来在两个线程之间共享它们。第一个线程获取互斥锁并设置 started 标志为 true,然后调用条件变量的 notify_one 方法通知第二个线程可以开始执行了。第二个线程在 while 循环中等待 started 标志变为 true,并在条件变量上调用 wait 方法,这将释放互斥锁并进入等待状态,直到被 notify_one 唤醒。
  这个示例展示了如何使用条件变量来实现线程同步,这是一种在并发编程中常用的技术。通过使用条件变量,我们可以避免在循环中使用忙等待的方式来等待某些条件的满足。
  7.3.Semaphore
  Semaphore(信号量)是一种用于控制并发访问的同步原语。它可以维护一个计数器,用于表示可用资源的数量。每当一个线程需要使用一个资源时,它会尝试获取一个信号量。如果信号量计数器的值大于零,那么线程将获取该资源并将计数器减少。如果计数器的值为零,那么线程将被阻塞,直到有一个资源可用为止。当一个线程使用完一个资源后,它会将计数器增加,以使其他线程能够使用该资源。
  在 Rust 中,可以使用 std::sync::Semaphore 来创建一个信号量,并使用 acquire 和 release 方法来进行获取和释放操作。
  以下是一个简单的示例,演示了如何使用 Semaphore 来限制同时执行的线程数量:use std::sync::Arc; use std::sync::Semaphore; use std::thread;  fn main() {     // 创建一个计数器为 2 的 Semaphore     let sem = Arc::new(Semaphore::new(2));      // 启动 5 个线程进行计数器操作     for i in 0..5 {         let sem_clone = sem.clone();         thread::spawn(move || {             // 获取信号量             sem_clone.acquire();              println!("Thread {} started", i);             thread::sleep(std::time::Duration::from_secs(2));             println!("Thread {} finished", i);              // 释放信号量             sem_clone.release();         });     }      // 等待所有线程执行完毕     thread::sleep(std::time::Duration::from_secs(5)); }
  在上面的示例中,我们首先创建了一个计数器为 2 的 Semaphore,并使用 Arc 来将其包装在一个可共享的引用计数指针中。接下来,我们启动了 5 个线程来执行操作。每个线程首先会尝试获取信号量,如果有可用的资源,那么它将执行其操作,并在完成后释放信号量,以便其他线程可以获取该资源。由于我们的 Semaphore 计数器只有 2,因此在任何给定时刻,最多只有 2 个线程可以同时执行。
  最后,我们在主线程中等待所有线程执行完毕,以便观察其输出。Rust 并发编程实践并发编程案例实践
  下面是一个简单的 Rust 并发编程案例实践,通过多线程实现并发下载多个网页:use std::thread;  fn main() {     let urls = vec![         "https://www.example.com/page1",         "https://www.example.com/page2",         "https://www.example.com/page3",         "https://www.example.com/page4",         "https://www.example.com/page5",     ];     let mut handles = vec![];      for url in urls {         let handle = thread::spawn(move || {             // 这里是下载网页的代码,例如使用 reqwest 库             // 下载成功后可以将网页保存到本地或者打印网页内容             println!("Downloaded {}", url);         });         handles.push(handle);     }      for handle in handles {         handle.join().unwrap();     } }
  在这个示例中,我们首先定义了需要下载的网页链接地址,然后通过 for 循环创建了多个线程,每个线程负责下载一个网页。在创建线程时,我们使用了 thread::spawn 函数来创建新的线程,并将需要下载的网页地址传递给线程,使用 move 关键字来将 url 的所有权转移给线程。
  在线程内部,我们可以编写下载网页的代码,并在下载成功后打印一条提示信息。最后,我们在主线程中使用 join 函数来等待所有线程执行完毕,并通过 unwrap 方法处理可能的错误。
  这个示例演示了如何使用 Rust 的多线程功能来实现并发下载多个网页,同时也涉及到了 Rust 的所有权和闭包等基础概念。Rust 并发编程项目实战
  这里提供一个简单的 Rust 并发编程项目示例:一个基于多线程和通道实现的简单的任务调度器。
  任务调度器包含多个 worker 线程和一个任务队列,它从队列中获取任务并将其分配给 worker 线程。每个 worker 线程都会不断地从队列中获取任务并执行。
  下面是示例代码:use std::sync::{Arc, Mutex}; use std::thread;  type Job = Box;  struct Worker {     id: usize,     thread: Option>, }  impl Worker {     fn new(id: usize, receiver: Arc>>) -> Self {         let thread = thread::spawn(move || loop {             let job = receiver.lock().unwrap().recv().unwrap();             println!("Worker {} got a job; executing.", id);             job();         });         Self {             id,             thread: Some(thread),         }     } }  pub struct ThreadPool {     workers: Vec,     sender: crossbeam::channel::Sender, }  impl ThreadPool {     pub fn new(size: usize) -> Self {         assert!(size > 0);          let (sender, receiver) = crossbeam::channel::unbounded::();         let receiver = Arc::new(Mutex::new(receiver));          let mut workers = Vec::with_capacity(size);         for id in 0..size {             workers.push(Worker::new(id, receiver.clone()));         }          Self { workers, sender }     }      pub fn execute(&self, f: F)     where         F: FnOnce() + Send + "static,     {         let job = Box::new(f);         self.sender.send(job).unwrap();     } }  impl Drop for ThreadPool {     fn drop(&mut self) {         for worker in &mut self.workers {             println!("Sending terminate message to worker {}", worker.id);             self.sender.send(Box::new(|| {})).unwrap();             if let Some(thread) = worker.thread.take() {                 thread.join().unwrap();             }         }     } }  fn main() {     let pool = ThreadPool::new(4);      for i in 0..8 {         pool.execute(move || {             println!("Executing job {} in thread {:?}", i, thread::current().id());             thread::sleep(std::time::Duration::from_secs(1));         });     }      std::thread::sleep(std::time::Duration::from_secs(5)); }
  这个示例实现了一个简单的线程池,并使用通道来实现任务的调度和分配。主线程创建了一个线程池,然后提交了 8 个任务。线程池会将这些任务分配给 4 个 worker 线程,每个线程会执行任务并打印出执行的任务编号。每个任务都会 sleep 1 秒钟,以模拟一些耗时的工作。最后,主线程 sleep 5 秒钟,等待所有任务执行完毕。
  这个示例展示了如何使用 Rust 的并发编程机制来实现一个简单的任务调度器。在实际的项目中,任务调度器可以用于实现并发任务处理、消息处理、网络服务器等。Rust 并发编程最佳实践
  以下是一些 Rust 并发编程的最佳实践:使用 std::sync::mpsc 而不是 std::sync::Arc>,这样可以避免一些潜在的死锁问题。尽量使用不可变数据,避免使用可变数据,这样可以避免并发访问导致的数据竞争问题。使用 std::thread::spawn 创建新的线程,而不是 std::thread::scoped,因为 scoped 可能会导致线程泄露。使用 std::sync::Once 来实现一次性初始化,这样可以避免多个线程同时初始化相同的资源。使用 std::sync::Barrier 来等待多个线程同时到达一个点,这样可以避免死锁和活锁问题。
  以下是一个 Rust 并发编程的示例,使用最佳实践来避免潜在的问题:use std::sync::{mpsc, Arc, Barrier, Mutex}; use std::thread;  fn main() {     let (tx, rx) = mpsc::channel();     let barrier = Arc::new(Barrier::new(3));     let data = Arc::new(Mutex::new(0));      for i in 0..3 {         let tx1 = tx.clone();         let barrier1 = barrier.clone();         let data1 = data.clone();          thread::spawn(move || {             // 等待所有线程都到达此处             barrier1.wait();              // 递增数据             let mut num = data1.lock().unwrap();             *num += i;              // 发送数据             tx1.send(*num).unwrap();         });     }      // 等待所有线程都执行完毕     for _ in 0..3 {         rx.recv().unwrap();     } }
  在这个示例中,我们使用了 std::sync::mpsc 来进行线程间通信,避免了使用 std::sync::Arc> 带来的潜在死锁问题。我们使用了 std::sync::Barrier 来等待所有线程都到达一个点,避免了死锁和活锁问题。我们也使用了不可变数据,避免了并发访问导致的数据竞争问题。总结
  在本篇学习笔记中,我们学习了 Rust 并发编程的基础知识,包括并发模型、线程基础、共享状态与可变性、锁和互斥体、Atomics 和 Memory Ordering、通道和消息传递等。我们还深入了解了 Rust 并发编程的高级主题,如生命周期和并发、异步编程和 Futures、异步编程和 Tokio 框架、并发编程中的性能优化、错误处理和并发编程、原子引用计数和并发计数器、高级同步构造如 Barrier、Condvar 和 Semaphore。
  同时,我们也探讨了 Rust 并发编程的实践,包括并发编程案例实践、Rust 并发编程项目实战和 Rust 并发编程最佳实践。通过这些实践,我们可以更加深入地了解 Rust 并发编程的应用。
  在下一篇学习笔记中,我们将探讨 Rust 中的泛型和 trait,泛型和 trait 是 Rust 中非常重要的概念,也是 Rust 语言的核心特性之一。我们将介绍泛型和 trait 的概念、使用方法和一些高级特性。敬请期待!

一个夜场女孩的故事(一)姐姐,还是昨天哪样的妆面,头发也做卷。她今天穿的是一件墨色改良式旗袍,娇小的身材却凹凸有致,一张标准的鹅蛋脸,五官精致完全没有整形的痕迹。脸颊带着一点和气质不相符的婴儿肥,成熟中带记忆中的华岩寺(散文)记忆中的华岩寺(散文杨永忠)每个人小时候喜欢玩耍的地方,总是让人记忆深刻,留下美好难忘的时光,就是许多年后,那份挚热的爱恋依然荡漾在自已心中的某个角落,永远忘却不去。前段时间因为照自由的责任要自由的人,其实要担最大的责任选别人少走的路的人,要背负最沉重的枷锁,从来就没有不需要重力的飞行印象中这是马薇薇在某期奇葩说当中的一段话。自由这个词现在出现的频次实在是太高了,大家老祖宗忠告人穷不交三友,落难不求三人,要记牢!世事总是变换不停,我们无法预知将来的人生路上,到底会发生什么。唯一能够以不变应万变的,就是培养出一颗无比强大的内心。俗语说人穷不交三友,落难不求三人。当自己处于人生低谷时,一定不要好,就从今天开始,做自己的英雄好,就从今天开始我们要做一个勇敢者,做自己的英雄。我们每一个人在这个星球上都是一个独一无二的个体一个主体。我们从一出生就是从上亿的细胞里面竞争中取得的胜利,其实我们的出生就代表一个科尔曼与福妮雅他在里面,正和福妮雅一起,相互保护着对方,不受任何外人的侵扰彼此,对对方而已,包容了整个世界。他们在里面跳舞,很可能光着身子,超越人世的苦难,置身于一个植根于世俗欲望的非世俗的天堂没有后台的人,如何让别人不敢招惹你?头条创作挑战赛没有后台的人,如何让别人不敢招惹你?你可以没有心机,没有城府,可以傻白甜,但是你一定要学会怎么样让别人算计不着你首先咱们必须要知道,别人算计你的底层逻辑是什么,那就是人老了,伴侣先走一步以后,真正有远见的老人不会做这3件事当一个人的伴侣先走一步,特别是在老年时,对于留下的那个人来说,无疑是一个巨大的打击和挑战。这时,一些老年人可能会陷入孤独和无助的境地,失去了信心和希望。但是,真正有远见的老人会采取想要有钱,必须扒掉这五层皮情感点评大赏为什么有那么多的人一辈子都在穷人堆里面打转?很简单,扒自己皮的滋味可不好受,你要是有本事,能扒掉你也行。第一层,扒掉懒惰的皮这层皮是最难扒的懒惰是人的天性,战胜本性上的把靖国神社放到封面上,大连理工大学出版社及时道歉,态度很诚恳根据潮新闻的报道,大连理工大学出版社出版了一本日本旅游攻略,名叫日本旅游一本书就够,封面上加入了日本靖国神社的图片,此事一经传出,全网哗然,纷纷谴责大连理工大学出版社的做法不妥,究0元!国内唯一的阿胶博物馆全员免费!探索这座城市的文化底蕴领略中医药文化的博大精深3月16日起中国阿胶博物馆正式向社会公众免费开放为大力弘扬中医药文化增强文化传承的民族自觉性全面提高人民群众对中医药文化的了解和认知切
教师因酒驾被开除公职,退休后有养老金吗?刘校长总算活过来啦!昨天我在地铁上遇到他,一个酒驾,让他整个人神情气质发生了脱胎换骨般的变化曾经的市优秀校长,省德育先进工作者,副高5级三十功名化作尘与土他悔啊!大意失荆州!若非去公司搬去日本,税后月薪约40w日元,值得去吗?很高兴能回答您的这个问题,本人在日本开公司五年,对日本薪资待遇略知一二。我们说一下日本平均工资吧,2019年日本人的平均月工资是30万6200日元。从日本全行业的平均值看,男性的月大学一年再回去复读值得吗?我谈一下我儿子的情况作标一一长春三年前我儿子以610分中考成绩考入吉林省实验中学,我当时想让他报十一高或二中,但在最后一刻他把十一高改成了省实验中学。入学第一年他闹心了一年,种种的为什么不能喝泡在水里面的饮料?1998年的特大暴雨,使江苏南部的小山村发生了特大洪灾,18岁的小刘从自家门前捞起了未开封的饮料,一口气喝了下去。结果意想不到的事情发生了。在98年7月12日,突如其来水灾,使小刘你在农村走夜路的时候,遇到过哪些可怕的事情?你在农村走夜路的时候,遇到过哪些可怕的事情,时间还要倒回到80年代,那时候农村里根本没有交通工具,别说小车,摩托车,电动车,连自行车也没有。那时一个县委书记下乡,骑着一辆自行车,戴在单位里吃饭,你看到领导孤零零的一个人在那里吃,你会过去和他坐在一起吃饭吗?如果在单位里,大概率是不要去的,有以下几个原因1不知道领导是不是在等人。2不知道领导是不是想一个人静静。3不知道领导是不是在独立思考问题。如果自己是有非常重要的事,就去,如果有一般在深圳,税前月薪4。5万什么水平?都说人贵有自知之明,如果阁下月薪4。5W,难道不自知在深圳处于什么水平?难道因为现实生活中太自卑,要来今日头条拿优越感?深圳与广州不同,深圳属于金领城市,能留下来的,月收入2W以上外地人在南通做什么工作一年才能赚10万块钱?外地人在南通怎么一年才能赚足十万?这说难也难,说简单也很简单。比如工地上,家具安装,很多,一天能有两三百的工资,只要踏实肯干一年也能赚个十来万。如果不想太劳累,买辆面包车或者金杯车深圳人到惠州养老可行吗?我也是深圳人准备去惠州惠城区买房定居的,中年了,说实话,自己没有大本事在深圳发大财,深圳竞争激烈,物价很高,人口密集,节奏很快,感觉每天都活得没有什么生命质量,我也没有孩子,我的小惠州10年内能否远甩东莞佛山珠海?惠州要在10年内远甩佛山?东莞?珠海?这句话一定是有语法错误,要么就是我看错了!如果说惠州远甩珠海的话,或许现在都已经是现实情况了!如果说惠州要在10年内远甩佛山和东莞,我一定认为在火车上有没有经历过细思极恐的事情?2018年我一个人乘坐动车,偶遇一个十五岁的女孩。女孩和我挨着坐在一起,就带着简单的行李,水和食物都没有带。开始的时候我以为小女孩和家人一起乘车,也没有太在意。查票的时候我看到女孩