Rust并发安全库or_poisoned的使用:处理Mutex锁中毒(Poisoning)的轻量级解决方案

Rust并发安全库or_poisoned的使用:处理Mutex锁中毒(Poisoning)的轻量级解决方案

内容中提供的示例:

use leptos::*;

#[component]
pub fn SimpleCounter(initial_value: i32) -> impl IntoView {
    // 使用初始值创建响应式信号
    let (value, set_value) = create_signal(initial_value);

    // 为按钮创建事件处理器
    // 注意:`value`和`set_value`是`Copy`,所以很容易将它们移动到闭包中
    let clear = move |_| set_value(0);
    let decrement = move |_| set_value.update(|value| *value -= 1);
    let increment = move |_| set_value.update(|value| *value += 1);

    // 使用声明式`view!`宏创建用户界面
    view! {
        <div>
            <button on:click=clear>Clear</button>
            <button on:click=decrement>-1</button>
            // 文本节点可以加引号或不加引号
            <span>"Value: " {value} "!"</span>
            <button on:click=increment>+1</button>
        </div>
    }
}

// 我们也支持构建器语法,而不是类似JSX的`view`宏
#[component]
pub fn SimpleCounterWithBuilder(initial_value: i32) -> impl IntoView {
    use leptos::html::*;

    let (value, set_value) = create_signal(initial_value);
    let clear = move |_| set_value(0);
    let decrement = move |_| set_value.update(|value| *value -= 1);
    let increment = move |_| set_value.update(|value| *value += 1);

    // 上面的`view`宏扩展为这种构建器语法
    div().child((
        button().on(ev::click, clear).child("Clear"),
        button().on(ev::click, decrement).child("-1"),
        span().child(("Value: ", value, "!")),
        button().on(ev::click, increment).child("+1")
    ))
}

// 易于与Trunk或简单的wasm-bindgen设置一起使用
pub fn main() {
    mount_to_body(|| view! {
        <SimpleCounter initial_value=3 />
    })
}

完整示例demo:

use std::sync::{Mutex, PoisonError};
use or_poisoned::OrPoisoned;

// 定义一个简单的数据结构
#[derive(Debug)]
struct Counter {
    value: i32,
}

impl Counter {
    fn new() -> Self {
        Counter { value: 0 }
    }
    
    fn increment(&mut self) {
        self.value += 1;
    }
    
    fn decrement(&mut self) {
        self.value -= 1;
    }
}

fn main() {
    // 创建一个被Mutex保护的计数器
    let counter = Mutex::new(Counter::new());
    
    // 模拟多个线程访问共享数据
    let handles: Vec<_> = (0..10).map(|i| {
        let counter = counter.clone();
        std::thread::spawn(move || {
            // 使用or_poisoned处理可能的锁中毒
            let result = counter.lock().or_poisoned();
            
            match result {
                Ok(mut guard) => {
                    // 安全地访问和修改数据
                    if i % 2 == 0 {
                        guard.increment();
                        println!("Thread {} incremented counter to {}", i, guard.value);
                    } else {
                        guard.decrement();
                        println!("Thread {} decremented counter to {}", i, guard.value);
                    }
                }
                Err(PoisonError { .. }) => {
                    // 处理锁中毒的情况
                    println!("Thread {} encountered a poisoned lock", i);
                }
            }
        })
    }).collect();
    
    // 等待所有线程完成
    for handle in handles {
        handle.join().unwrap();
    }
    
    // 最终检查计数器的值
    let final_counter = counter.lock().or_poisoned();
    match final_counter {
        Ok(guard) => {
            println!("Final counter value: {}", guard.value);
        }
        Err(_) => {
            println!("Lock was poisoned, unable to get final value");
        }
    }
}

#[test]
fn test_or_poisoned() {
    let data = Mutex::new(42);
    
    // 正常情况下的使用
    let result = data.lock().or_poisoned();
    assert!(result.is_ok());
    
    // 模拟锁中毒的情况
    let poisoned_data = Mutex::new(100);
    let _guard = poisoned_data.lock().unwrap();
    // 这里故意panic来导致锁中毒
    std::panic::catch_unwind(|| {
        panic!("This will poison the lock");
    }).ok();
    
    // 使用or_poisoned处理中毒的锁
    let poisoned_result = poisoned_data.lock().or_poisoned();
    assert!(poisoned_result.is_err());
}

关于框架

Leptos是一个全栈、同构的Rust Web框架,利用细粒度响应式构建声明式用户界面。

这意味着什么?

  • 全栈:Leptos可用于构建在浏览器中运行的应用程序(客户端渲染)、在服务器上运行的应用程序(服务器端渲染),或通过在服务器上渲染HTML然后在浏览器中添加交互性(带水合的服务器端渲染)来构建应用程序。这包括支持数据和HTML的HTTP流式传输。

  • 同构:Leptos提供了编写同构服务器函数的原语,即可以在客户端或服务器上以"相同形状"调用的函数,但只在服务器上运行。这意味着您可以将服务器端逻辑(数据库请求、身份验证等)与将使用它的客户端组件一起编写,并像在浏览器中运行一样调用服务器函数,而无需创建和维护单独的REST或其他API。

  • Web:Leptos构建在Web平台和Web标准之上。路由器设计为使用Web基础(如链接和表单)并在其之上构建,而不是试图替换它们。

  • 框架:Leptos提供了构建现代Web应用程序所需的大部分内容:响应式系统、模板库以及在服务器和客户端都能工作的路由器。

  • 细粒度响应式:整个框架构建自响应式原语。这使得代码具有极高的性能且开销最小:当响应式信号的值发生变化时,它可以更新单个文本节点、切换单个类或将元素从DOM中移除,而无需运行任何其他代码。(因此,没有虚拟DOM开销!)

  • 声明式:告诉Leptos您希望页面看起来如何,让框架告诉浏览器如何实现。

了解更多

以下是学习更多关于Leptos的资源:

  • 书籍(进行中)
  • 示例
  • API文档
  • 常见错误(以及如何修复它们!)

nightly说明

大多数示例假设您正在使用Rust的nightly版本和Leptos的nightly功能。要使用nightly Rust,您可以在全局或每个项目的基础上设置工具链。

要将nightly设置为所有项目的默认工具链(如果您还没有添加将Rust编译为WebAssembly的能力):

rustup toolchain install nightly
rustup default nightly
rustup target add wasm32-unknown-unknown

如果您只想在Leptos项目中使用nightly,请添加具有以下内容的rust-toolchain.toml文件:

[toolchain]
channel = "nightly"
targets = ["wasm32-unknown-unknown"]

nightly功能启用了用于访问和设置信号的函数调用语法,而不是.get()和.set()。这导致了一个一致的心智模型,其中访问任何类型的响应式值(信号、备忘录或派生信号)始终表示为函数调用。这只有在nightly Rust和nightly功能下才可能。

cargo-leptos

cargo-leptos是一个构建工具,旨在轻松构建在客户端和服务器上运行的应用程序,并具有无缝集成。现在开始一个真正的Leptos项目的最佳方式是使用cargo-leptos和我们为Actix或Axum的入门模板。

cargo install cargo-leptos
cargo leptos new --git https://github.com/leptos-rs/start
cd [your project name]
cargo leptos watch

打开浏览器访问http://localhost:3000/。

常见问题解答

名称的含义是什么?

Leptos(λεπτός)是一个古希腊词,意思是"薄、轻、精致、细粒度"。对我(一个古典学者,不是狗主人)来说,它唤起了为框架提供动力的轻量级响应式系统。我后来了解到同一个词是医学术语"钩端螺旋体病"的词根,这是一种影响人类和动物的血液感染…我的错。在创建这个框架时没有伤害任何狗。

它是否可用于生产?

人们通常通过这个问题表达以下三种含义之一。

  1. API是否稳定? 即,我是否需要从Leptos 0.1重写整个应用到0.2到0.3到0.4,或者我现在可以编写它并随着新版本的发布受益于新功能和更新?

API基本上已经确定。我们正在添加新功能,但我们对类型系统和模式的位置非常满意。我不期望您的代码会有重大破坏性更改来适应未来的版本,就架构而言。

  1. 有错误吗?

是的,我确定有。您可以从我们的问题跟踪器的状态随时间变化看出,没有那么多错误,并且它们通常很快得到解决。但肯定的是,可能会有一些时刻您遇到需要在框架级别修复的问题,这可能不会立即解决。

  1. 我是消费者还是贡献者?

这可能是重要的一点:"生产就绪"意味着对库的某种定位:您可以基本上使用它,而无需任何关于其内部结构的特殊知识或贡献能力。每个人在某种程度上都在他们的堆栈中有这个:例如,我(@gbj)目前没有能力或知识来贡献像wasm-bindgen这样的东西:我只是依赖它工作。

社区中有几个人现在在工作中使用Leptos来构建内部应用程序,他们也成为了重要的贡献者。我认为这是目前生产使用的正确水平。可能会有您需要的缺失功能,您最终可能会构建它们!但对于内部应用程序,如果您愿意在过程中构建和贡献缺失的部分,该框架现在绝对可用。

我可以将其用于本地GUI吗?

当然!显然view宏用于生成DOM节点,但您可以使用响应式系统来驱动任何使用与DOM相同类型的面向对象、基于事件回调的框架的本地GUI工具包。原则是相同的:

  • 使用信号、派生信号和备忘录创建您的响应式系统
  • 创建GUI小部件
  • 使用事件监听器更新信号
  • 创建效果来更新UI

我整理了一个非常简单的GTK示例,以便您理解我的意思。

为0.7开发的新渲染方法支持"通用渲染",即它可以使用支持一小组6-8个函数的任何渲染库。(这旨在作为典型保留模式、面向对象风格GUI工具包(如DOM、GTK等)之上的层。)未来的渲染工作将允许以更类似于Web框架使用的声明式方法创建本地UI。

这与Yew有何不同?

Yew是用于Rust Web UI开发的最常用库,但Yew和Leptos在哲学、方法和性能方面存在一些差异。

  • VDOM与细粒度: Yew构建在虚拟DOM(VDOM)模型上:状态变化导致组件重新渲染,生成新的虚拟DOM树。Yew将其与先前的VDOM进行比较,并将这些补丁应用到实际DOM。组件函数在状态变化时重新运行。Leptos采用完全不同的方法。组件运行一次,创建(并返回)实际DOM节点,并设置响应式系统来更新这些DOM节点。

  • 性能: 这对性能有巨大影响:Leptos在创建和更新UI方面都比Yew快得多。

  • 服务器集成: Yew创建于浏览器渲染的单页应用程序(SPA)是主导范式的时代。虽然Leptos支持客户端渲染,但它也专注于通过服务器函数和多种提供HTML的模式(包括无序流式传输)与应用程序的服务器端集成。

这与Dioxus有何不同?

像Leptos一样,Dioxus是一个使用Web技术构建UI的框架。然而,在方法和功能上存在显著差异。

  • VDOM与细粒度: 虽然Dioxus有一个高性能的虚拟DOM(VDOM),但它仍然使用粗粒度/组件范围的响应式:更改有状态值会重新运行组件函数并将旧UI与新UI进行比较。Leptos组件使用不同的心智模型,创建(并返回)实际DOM节点并设置响应式系统来更新这些DOM节点。

  • Web与桌面优先级: Dioxus在其全栈模式中使用Leptos服务器函数,但没有相同的基于<Suspense>的支持,例如流式HTML渲染,或共享对整体Web性能的相同关注。Leptos倾向于优先考虑整体Web性能(流式HTML渲染、更小的WASM二进制大小等),而Dioxus在构建桌面应用程序时具有无与伦比的体验,因为您的应用程序逻辑作为本机Rust二进制文件运行。

这与Sycamore有何不同?

Sycamore和Leptos都深受SolidJS的影响。在这一点上,Leptos拥有更大的社区和生态系统,并且开发更活跃。其他差异:

  • 模板DSL: Sycamore为其视图使用自定义模板语言,而Leptos使用类似JSX的模板格式。
  • ’static信号: Leptos的主要创新之一是创建了Copy + 'static信号,具有出色的易用性。Sycamore正在采用相同的模式,但这尚未发布。
  • Perseus与服务器函数: Perseus元框架提供了一种构建包含服务器功能的Sycamore应用程序的固执己见的方式。Leptos相反在框架核心提供像服务器函数这样的原语。

1 回复

or_poisoned:Rust中处理Mutex锁中毒的轻量级解决方案

介绍

or_poisoned是一个轻量级Rust库,专门用于简化Mutex锁中毒(Poisoning)的处理。当持有Mutex锁的线程panic时,标准库会将Mutex标记为"中毒"状态。or_poisoned提供了一种优雅的方式来处理这种情况,避免了繁琐的错误处理代码。

使用方法

安装

Cargo.toml中添加依赖:

[dependencies]
or_poisoned = "0.1"

基本用法

use std::sync::{Mutex, Arc};
use std::thread;
use or_poisoned::MutexExt;

fn main() {
    let data = Arc::new(Mutex::new(0));
    
    // 模拟panic导致锁中毒
    let handle = thread::spawn({
        let data = Arc::clone(&data);
        move || {
            let mut guard = data.lock().unwrap();
            *guard += 1;
            panic!("故意panic导致锁中毒");
        }
    });
    
    // 等待线程结束(会panic)
    let _ = handle.join();
    
    // 使用or_poisoned处理中毒的锁
    let result = data.lock().or_poisoned(|mut guard| {
        // 即使锁中毒,也能安全访问数据
        *guard += 1;
        *guard
    });
    
    println!("最终值: {}", result);
}

高级用法:自定义错误处理

use std::sync::{Mutex, PoisonError};
use or_poisoned::MutexExt;

fn process_data() -> Result<i32, String> {
    let data = Mutex::new(42);
    
    data.lock().or_poisoned_with(|mut guard, err: PoisonError<_>| {
        // 自定义错误处理逻辑
        eprintln!("锁中毒警告: {}", err);
        
        // 可以继续使用guard,或者返回自定义错误
        *guard = 100; // 重置数据
        Ok(*guard)
    })
}

fn main() {
    match process_data() {
        Ok(value) => println!("处理成功: {}", value),
        Err(e) => println!("处理失败: {}", e),
    }
}

链式调用示例

use std::sync::Mutex;
use or_poisoned::MutexExt;

fn main() {
    let counter = Mutex::new(0);
    
    let result = counter.lock()
        .or_poisoned(|mut g| {
            *g += 1;
            *g
        })
        .and_then(|value| {
            // 可以继续处理结果
            Ok(value * 2)
        });
    
    println!("结果: {:?}", result);
}

完整示例demo

use std::sync::{Mutex, Arc, PoisonError};
use std::thread;
use std::time::Duration;
use or_poisoned::MutexExt;

// 定义一个共享数据结构
struct SharedData {
    counter: Mutex<i32>,
    log: Mutex<Vec<String>>,
}

impl SharedData {
    fn new() -> Self {
        Self {
            counter: Mutex::new(0),
            log: Mutex::new(Vec::new()),
        }
    }

    // 使用or_poisoned安全地增加计数器
    fn increment(&self) -> Result<i32, String> {
        self.counter.lock().or_poisoned_with(|mut guard, err| {
            // 记录中毒事件到日志
            self.log.lock().or_poisoned(|mut log_guard| {
                log_guard.push(format!("计数器锁中毒: {}", err));
            });

            // 重置计数器并继续操作
            *guard = 0;
            *guard += 1;
            Ok(*guard)
        })
    }

    // 安全地添加日志条目
    fn add_log_entry(&self, entry: String) -> Result<(), String> {
        self.log.lock().or_poisoned_with(|mut guard, err| {
            // 如果日志锁中毒,创建新的日志向量
            eprintln!("日志锁中毒,创建新日志: {}", err);
            *guard = Vec::new();
            guard.push(entry);
            Ok(())
        })
    }

    // 获取当前状态
    fn get_status(&self) -> Result<(i32, Vec<String>), String> {
        let counter = self.counter.lock().or_poisoned(|g| *g)?;
        let log = self.log.lock().or_poisoned(|g| g.clone())?;
        Ok((counter, log))
    }
}

fn main() {
    let shared_data = Arc::new(SharedData::new());

    // 创建多个工作线程
    let mut handles = vec![];

    for i in 0..3 {
        let data = Arc::clone(&shared_data);
        let handle = thread::spawn(move || {
            // 模拟一些工作
            thread::sleep(Duration::from_millis(100 * i as u64));

            if i == 1 {
                // 第二个线程会panic,导致锁中毒
                let mut guard = data.counter.lock().unwrap();
                *guard += 10;
                panic!("线程 {} 故意panic!", i);
            } else {
                // 其他线程正常操作
                match data.increment() {
                    Ok(value) => println!("线程 {}: 计数器增加到 {}", i, value),
                    Err(e) => eprintln!("线程 {}: 错误 - {}", i, e),
                }

                data.add_log_entry(format!("线程 {} 完成工作", i)).unwrap();
            }
        });
        handles.push(handle);
    }

    // 等待所有线程完成
    for handle in handles {
        let _ = handle.join();
    }

    // 获取最终状态
    match shared_data.get_status() {
        Ok((counter, log)) => {
            println!("最终计数器值: {}", counter);
            println!("日志条目:");
            for entry in log {
                println!("  - {}", entry);
            }
        }
        Err(e) => eprintln!("获取状态失败: {}", e),
    }

    // 演示链式调用
    println!("\n演示链式调用:");
    let demo_data = Mutex::new(5);
    let result = demo_data.lock()
        .or_poisoned(|mut g| {
            *g *= 2;
            *g
        })
        .and_then(|value| {
            if value > 10 {
                Ok(value)
            } else {
                Err("值太小".to_string())
            }
        });

    match result {
        Ok(value) => println!("链式调用成功: {}", value),
        Err(e) => println!("链式调用失败: {}", e),
    }
}

特性

  • 轻量级: 极小的性能开销
  • 易用: 简化错误处理代码
  • 灵活: 支持自定义错误处理逻辑
  • 安全: 保持Rust的所有权和安全保证

适用场景

  • 需要简化Mutex中毒处理的应用程序
  • 希望保持代码简洁的并发程序
  • 需要自定义锁中毒恢复策略的项目

这个库特别适合那些希望保持代码简洁同时需要健壮处理锁中毒情况的Rust开发者。

回到顶部