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

从单线程小菜鸟到可以轻松地应对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 的概念、使用方法和一些高级特性。敬请期待!

屏下摄像头,为什么不受待见了?当手机屏幕越来越大,你还会在意那个小黑点吗?全面屏技术出现后,打造100屏占比的手机成了各品牌努力的共同目标,在这几年里,我们看到手机正面按键完全消失,通话听筒被巧妙隐藏,但唯有前1500多天没有停机,我持续看好ADA持的5个理由尽管当今世界上存在超过10,000种数字货币,但没有多少加密货币像卡尔达诺那样拥有光明的未来。流行的加密货币项目的开发人员一直在幕后工作,以确保网络具有无与伦比的安全性高效的交易快高圆圆高级凡尔赛自认没有做演员的天分,只是长得太漂亮了网友说,现在已经很少见敢于反省自己的演员了。就算有,他们也不会对着镜头说出来。之所以有这样的评价,是高圆圆在节目十三邀中对自己作为演员的身份,给出了一句这样的概括。她说我是一个形象这天气不能穿太少这天气还正值春寒料峭,气温还比较低,却不想就有女孩子把冬天衣服换掉,穿上了近似夏天的衣服。一件抹胸内衣,齐肚脐马甲,紧身牛仔裤,或者干脆一件超短裙,配上长丝袜。在大街上走,确实很亮珍惜每一次打洞的机会经过开年的几轮急跌,股市本已进入震荡筑底阶段。然而,乌俄战争的不断升级打破了这种宁静,超4000只股票下跌的惨淡行情,重回市场。基民们刚缓和下来的情绪,又躁动起来。还要跌多久?什么美国排名第一的热水器巨头年营收223亿元,满意度超过海尔美的热水器是必备的家电产品之一。去年国内电热水器零售量2067万台,燃气热水器零售量1304万台,同比分别下降5。6和1。2。百户保有量超过90,规模增长已经明显放缓。市面上品牌琳琅满2022年不买房,5年后房子更买不起还是随便挑?央媒发声定调不知不觉,2022年已经过去2个多月了,进入3月份,楼市行情依旧扑朔迷离。一方面,在春节假期之后,全国已经有超过40个城市出台了楼市利好政策,特别是在青岛和郑州这类大型城市楼市调控简单容学适合1岁宝宝的一道菜蒸肉饼这道菜家常很适合1岁以后小朋友的一道菜,孩子不爱吃肉,或者吞咽不下,又不吃辣椒,这个肉饼简单易做又营养,好吃不上火,用料肉馅200克山药玉米半根淀粉2勺蒸鱼豉油2勺盐少许蚝油1勺花孩子喜欢吃手指,家长要干预吗?文张亚停中山大学孙逸仙纪念医院儿科医生门诊中经常有家长咨询孩子喜欢吃手指的问题。比如帮他把手指从口里拿出来后,孩子很快又把手指伸进嘴里。那么,孩子吃手指正常吗?有什么方法能让其戒掉有痤疮别用蒸脸仪来源生命时报陆军军医大学西南医院皮肤科教授杨希川如今,蒸脸仪成为炙手可热的美容工具,几乎每个爱美人士手中都有一个,商家更是宣称它能补水美容助吸收,蒸脸仪真有这么神奇吗?实际上,蒸汽外卖佣金之争商家平台何以续命?图片来源视觉中国外卖平台是有贡献的,我的观点并不是要做一个一棍子打死人的二极管,而是随着外卖平台的野蛮生长,出现了很多影响社会发展的状况,比如骑手待遇问题商家收益问题食品安全和品质
2022钢铁产业互联网大会举行钢铁产品有标签可溯源新民晚报讯(记者叶薇)由欧冶云商承办的2022钢铁产业互联网大会昨天举行。会议采用线下实体会场和元宇宙虚拟会场国内主会场和海外新加坡分会场相结合的形式,邀请钢铁生态圈各方共同探讨钢双十一优惠多多,三星GalaxyZFold4折叠屏换机首选在这个快速发展的时代,通讯行业也发展到了折叠屏的时代,从根本上解决了智能机形态单一的问题。目前三星已经是折叠屏领域的标杆。三星旗下第四代折叠屏手机GalaxyZFold4,也取得了曾1小时卖出35万台,三星直屏6nm芯片5000mAh,仅1199元手机厂商之间的竞争从来都没有停止过,新的处理器首发给谁?618特供机双11特供机该怎么选?降价幅度哪家更高?想要在国产手机市场这片红海中生存下来,不仅要有胆识更要有财力。而要说在中看月全食时,你有没有思考过这个问题?11月8日迎来今年第二次月全食。地球出现月全食时,月球将发生什么?中科院紫金山天文台科普主管王科超说,地球上之所以出现月全食,是因为月地日行到一条直线时,月球进入地球的本影,太阳投这是我见过最好看的手机外观设计现在许多人都对手机的颜值有比较高的要求,而今天我无意中发现了一款手机,这款手机的外观可以说是非常好看的。因为他采用梵高星空图为背景,利用独特的梵高纹理光刻技术绘制出了梵高星空图,不2022年双十一,哪些电视值得购买作为家电老司机,电视是最熟悉的品类,二狗就简单说说几款比较值得入手的电视产品吧,需要的小伙伴自取。入门和高性价比电视推荐小米ES系列红米X2022款系列红米XProOPPOK9系列抖音物流,套了菜鸟的壳?电商竞争本质是供应链竞争。物流等环节的自主可控,既是降本提效的基础,也是向价值链延伸的基石。抖音物流目前更类似早期的菜鸟网络,但会随着发展而不断演化,预计业务规模达到一定程度后不排西瓜视频关于打击用刷金币骗取流量行为的公告近期,西瓜视频在站内发现一批以刷金币为噱头骗取流量的作者在标题或封面上注明送你阅读惊喜奖励领金币阅读越多奖励越多等字样,欺骗和诱导用户观看内容,以此骗取流量,助长了社区的不良创作风数智时代,人才管理的经营逻辑ampampquot提效ampampquot篇事实上,我们不能只考虑人工成本。因为投入人工成本本质上为了产生收入,单纯降低人工成本可能导致的后果就是降低收入,这恐怕不是企业想要的结果,否则公司关门了,不就没有亏损了吗?在人事费第77节WebWorkers零点程序员王唯JavaScript是单线程的,但WebWorkers规范允许Web应用程序可以在独立于主线程的后台线程中,创建子线程,运行一个脚本操作,从而可以在独立的线程中执行费时的处理任务,近年来苹果手机的最大升级,iPhone15四大新设计曝光一代更比一代香,苹果手机的每一次更新换代,都会分分钟让人按捺不住钱包。在iPhone14推出不久,大家还在发愁抢不到货的时候,iPhone15的各种消息就蠢蠢欲动了,四大新设计曝光