理解 Rust 中的线程安全 RwLock> 机制

2024-01-06

背景

I am 全新的Rust(昨天开始),我试图确保我理解正确。我正在寻找为“游戏”编写一个配置系统,并希望它能够快速访问但偶尔可变。首先,我想研究本地化,这似乎是静态配置的合理用例(因为我意识到这些东西通常不会“生锈”)。我想出了以下(工作)代码,部分基于这篇博文 https://blog.sentry.io/2018/04/05/you-cant-rust-that(通过发现这个问题 https://stackoverflow.com/a/65200809)。我已将其包含在此处供参考,但现在请随意跳过它......

#[macro_export]
macro_rules! localize {
    (@single $($x:tt)*) => (());
    (@count $($rest:expr),*) => (<[()]>::len(&[$(localize!(@single $rest)),*]));

    ($name:expr $(,)?) => { LOCALES.lookup(&Config::current().language, $name) };
    ($name:expr, $($key:expr => $value:expr,)+) => { localize!(&Config::current().language, $name, $($key => $value),+) };
    ($name:expr, $($key:expr => $value:expr),*) => ( localize!(&Config::current().language, $name, $($key => $value),+) );

    ($lang:expr, $name:expr $(,)?) => { LOCALES.lookup($lang, $name) };
    ($lang:expr, $name:expr, $($key:expr => $value:expr,)+) => { localize!($lang, $name, $($key => $value),+) };
    ($lang:expr, $name:expr, $($key:expr => $value:expr),*) => ({
        let _cap = localize!(@count $($key),*);
        let mut _map : ::std::collections::HashMap<String, _>  = ::std::collections::HashMap::with_capacity(_cap);
        $(
            let _ = _map.insert($key.into(), $value.into());
        )*
        LOCALES.lookup_with_args($lang, $name, &_map)
    });
}

use fluent_templates::{static_loader, Loader};
use std::sync::{Arc, RwLock};
use unic_langid::{langid, LanguageIdentifier};

static_loader! {
    static LOCALES = {
        locales: "./resources",
        fallback_language: "en-US",
        core_locales: "./resources/core.ftl",
        // Removes unicode isolating marks around arguments, you typically
        // should only set to false when testing.
        customise: |bundle| bundle.set_use_isolating(false)
    };
}
#[derive(Debug, Clone)]
struct Config {
    #[allow(dead_code)]
    debug_mode: bool,
    language: LanguageIdentifier,
}

#[allow(dead_code)]
impl Config {
    pub fn current() -> Arc<Config> {
        CURRENT_CONFIG.with(|c| c.read().unwrap().clone())
    }
    pub fn make_current(self) {
        CURRENT_CONFIG.with(|c| *c.write().unwrap() = Arc::new(self))
    }
    pub fn set_debug(debug_mode: bool) {
        CURRENT_CONFIG.with(|c| {
            let mut writer = c.write().unwrap();
            if writer.debug_mode != debug_mode {
                let mut config = (*Arc::clone(&writer)).clone();
                config.debug_mode = debug_mode;
                *writer = Arc::new(config);
            }
        })
    }
    pub fn set_language(language: &str) {
        CURRENT_CONFIG.with(|c| {
            let l: LanguageIdentifier = language.parse().expect("Could not set language.");
            let mut writer = c.write().unwrap();
            if writer.language != l {
                let mut config = (*Arc::clone(&writer)).clone();
                config.language = l;
                *writer = Arc::new(config);
            }
        })
    }
}

impl Default for Config {
    fn default() -> Self {
        Config {
            debug_mode: false,
            language: langid!("en-US"),
        }
    }
}

thread_local! {
    static CURRENT_CONFIG: RwLock<Arc<Config>> = RwLock::new(Default::default());
}

fn main() {
    Config::set_language("en-GB");
    println!("{}", localize!("apologize"));
}

为简洁起见,我没有包含测试。我欢迎有关的反馈localize宏也是如此(因为我不确定我是否做对了)。

Question

理解Arc cloning

然而,我的主要问题是特别是在这段代码上(有一个类似的例子set_language too):

    pub fn set_debug(debug_mode: bool) {
        CURRENT_CONFIG.with(|c| {
            let mut writer = c.write().unwrap();
            if writer.debug_mode != debug_mode {
                let mut config = (*Arc::clone(&writer)).clone();
                config.debug_mode = debug_mode;
                *writer = Arc::new(config);
            }
        })
    }

虽然这有效,但我想确保这是正确的方法。据我了解

  1. 获取配置 Arc 结构上的写锁。
  2. 检查更改,如果更改:
  3. Calls Arc::clone()在作者身上(这会自动DeRefMut克隆之前 Arc 的参数)。这实际上并没有“克隆”结构,而是增加了引用计数器(所以应该很快)?
  4. Call Config::clone由于步骤 3 包含在 (*...) 中 - 这是正确的方法吗?我的理解是这样的does现在克隆Config,生成一个可变的拥有实例,然后我可以对其进行修改。
  5. 改变新的配置设置新的debug_mode.
  6. 创建一个新的Arc<Config>从这个拥有的Config.
  7. 更新静态 CURRENT_CONFIG。
  8. 将参考计数器释放到旧的Arc<Config>(如果当前没有其他东西使用内存,则可能会释放内存)。
  9. 释放写锁。

如果我理解正确的话,那么第 4 步中只会发生一次内存分配。对吗?第 4 步是解决此问题的正确方法吗?

了解性能影响

同样,这段代码:

LOCALES.lookup(&Config::current().language, $name)

正常使用下应该很快,因为它使用此功能:

    pub fn current() -> Arc<Config> {
        CURRENT_CONFIG.with(|c| c.read().unwrap().clone())
    }

它获得一个指向当前配置的引用计数指针,而不实际复制它(clone()应该打电话Arc::clone()如上所述),使用读锁(除非发生写操作,否则速度很快)。

理解thread_local!宏使用

如果这一切都很好,那就太好了!然而,我却陷入了最后一段代码:

thread_local! {
    static CURRENT_CONFIG: RwLock<Arc<Config>> = RwLock::new(Default::default());
}

这肯定是错误的吗?为什么我们要创建 CURRENT_CONFIG 作为thread_local。我的理解(诚然来自其他语言,结合有限的文档 https://doc.rust-lang.org/std/macro.thread_local.html)意味着当前正在执行的线程将有一个唯一的版本,这是没有意义的,因为线程不能中断自身?通常我会期望一个真正静态的RwLock跨多线程共享?我是否误解了什么或者这是一个错误原始博客文章 https://blog.sentry.io/2018/04/05/you-cant-rust-that?

事实上,下面的测试似乎证实了我的怀疑:

    #[test]
    fn config_thread() {
        Config::set_language("en-GB");
        assert_eq!(langid!("en-GB"), Config::current().language);
        let tid = thread::current().id();
        let new_thread =thread::spawn(move || {
            assert_ne!(tid, thread::current().id());
            assert_eq!(langid!("en-GB"), Config::current().language);
        });

        new_thread.join().unwrap();
    }

产生(证明配置未跨线程共享):

thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `LanguageIdentifier { language: Language(Some("en")), script: None, region: Some(Region("GB")), variants: None }`,
 right: `LanguageIdentifier { language: Language(Some("en")), script: None, region: Some(Region("US")), variants: None }`

在我看来,您所指的博客文章的部分不是很好。

你是对的RwLock这是假的 - 它可以替换为RefCell因为它是线程本地的。

博客文章中这种方法的理由是站不住脚的:

然而,在前面的示例中,我们引入了内部可变性。想象一下,我们有多个线程正在运行,所有线程都引用相同的配置,但其中一个线程翻转了一个标志。现在不希望标志随机翻转的并发运行代码会发生什么情况?

整个点aRwLock是在对象被锁定以供读取时无法进行修改(即RwLockReadGuard从返回RwLock::read()活着)。所以一个Arc<RwLock<Config>>当读锁被取出时,不会让你的标志“随机翻转”。 (当然,如果您释放锁并再次获取锁,并假设标志在此期间没有更改,这可能是一个问题。)

本节也没有指定如何实际进行配置更新。您需要一种机制来向其他线程发出配置更改已发生的信号(例如通道),并且线程本身必须使用新配置更新自己的线程局部变量。

最终,我认为该部分是一个糟糕的建议,而且当然不适合初学者。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

理解 Rust 中的线程安全 RwLock> 机制 的相关文章

随机推荐

  • Spring安全为所有角色名称添加了前缀“ROLE_”?

    我的网络安全配置中有以下代码 Override protected void configure HttpSecurity http throws Exception http authorizeRequests antMatchers a
  • FxCop 安装

    我正在尝试获取最新版本的 FxCop 我使用的是 Visual Studio 2010 Professional 版本 它不包含代码分析 因此我尝试使用 FxCop 进行设置 At http www microsoft com en us
  • 如何从 Python 3.10 反编译 .pyc 文件?

    我确实尝试过 uncompyle6 decompyl3 等 但它们都不适用于 3 10 现在是否有可能做到这一点 使用pycdc GitHub https github com zrax pycdc https github com zra
  • 在 Windows 上运行 MySQL 或 MariaDB 服务器的最少文件 - (便携式 MySQL/MariaDB)

    我正在寻找 MariaDB 或 MySQL 运行所需的最少文件 而无需像便携式服务器一样安装它 我更喜欢 MariaDB 而不是 MySQL 我想将它嵌入到用 Delphi 编写的软件中 我的软件将能够根据需要启动和停止服务器 我用谷歌搜索
  • Nivo 滑块:第一次显示时第一张图像缩放不成比例

    我在 div 中使用 Nivo Slider jQuery 插件 该 div 的尺寸比我正在显示的图像小 当滑块滑动到第二个图像时 图像上的图像会正确缩小 但是 当第一次显示第一个图像时 加载页面后立即 它仅在宽度上缩放 而在高度上不缩放
  • 如何重新映射 python 字典键

    我正在开发一个程序 该程序 除其他外 读取 CSV 文件 它以以下形式存储为字典数组 col1 data1a col2 data2a col1 data1b col2 data2b 对于每一行 作为其他处理的一部分 我需要将这些键重新映射到
  • 更改 UITableView 的节页眉/页脚标题而不重新加载整个表视图

    有没有办法在不调用的情况下重新加载表视图的节页眉 页脚 tableView reloadData 事实上 我想在表视图的部分页脚中显示表格视图部分中的单元格数量 表视图是可编辑的 我使用删除或插入行 insertRowsAtIndexPat
  • Android:将 PNG ByteArray 写入文件

    我已将图像文件读入 ByteArray 但如何将其写回 我的意思是将 ByteArray 保存到文件系统中的图像文件 首选 PNG 格式 我的代码从 PNG 文件到 ByteArray ByteArrayOutputStream strea
  • 使用 Quartz 跨多个无状态应用程序服务器调度单个作业

    我在负载均衡器后面有一层相同的应用程序服务器 出于操作原因 我有一个限制 即两个应用程序服务器上的应用程序配置必须相同 以便可以轻松添加和删除节点 所有应用程序服务器共享相同的数据库 应用程序服务器不会 不会集群 到目前为止 这一切都运行良
  • 在片段中初始化字体

    我有这一行 Typeface font Typeface createFromAsset getAssets fonts Delius Regular ttf but the getAssets 参数似乎带来了一些错误 它带有可怕的红线下划
  • 为什么我们要在 YARN 中配置 mapred.job.tracker?

    据我所知 YARN 被引入并取代了 JobTracker 和 TaskTracker 我看过一些Hadoop 2 6 0 2 7 0安装教程 他们正在配置mapreduce framework name作为纱线和mapred job tra
  • 在运行时调整 char[] 的大小

    我需要调整大小char array size to char array new size 在运行时 我怎样才能做到这一点 如果您正在使用std vector
  • ASP.NET MVC 3:具有继承/多态性的 DefaultModelBinder

    首先 对这篇大文章 我尝试先做一些研究 以及针对同一问题的技术组合 ASP NET MVC 3 Ninject 和 MvcContrib 表示歉意 我正在使用 ASP NET MVC 3 开发一个项目来处理一些客户订单 简而言之 我有一些继
  • 有没有办法生成 DOCX 文件的缩略图?

    我已经使用像 ASPOSE 这样的 付费 工具完成了这项工作 但我很好奇是否有任何开源工具可以做到这一点 我确信可能有工具可以做到这一点 但如果您可以将文件转换为可以轻松光栅化的格式 那么可能值得探索 例如 将工作文档转换为 pdf 然后对
  • 对从 JSON 创建的数据框应用过滤条件

    我正在处理由 JSON 创建的数据帧 然后我想在数据帧上应用过滤条件 val jsonStr metadata key 84896 value 54 key 1234 value 12 val rdd sc parallelize Seq
  • 命名空间不能直接包含成员... + 类型或命名空间定义,或文件结尾预期错误

    我正在尝试编译适用于 Windows Phone 的 Sync Framework 4 0 的示例代码 但是我在几个文件中遇到了错误 这些文件之一是 if SERVER namespace Microsoft Synchronization
  • 我如何从密钥库中获取秘密?

    我想从 Azure 密钥保管库获取机密 我找到了下面的代码并尝试了它 但我因错误而失败 private String clientId i private String secret i KeyVaultClient client new
  • 我什么时候应该使用 out 参数?

    我不明白何时应该使用输出参数 如果我需要返回多个类型 我个人会将结果包装在新类型中 我发现使用它比使用 out 更容易 我见过这样的方法 public void Do int arg1 int arg2 out int result 在某些
  • 使用 php 发送带有附件的电子邮件

    我使用此代码使用 php 发送带有附件的电子邮件 但附件中存在一些错误 因为我收到了一封电子邮件并且附件出现在内容中 在我使用相同的代码并成功运行之前 为什么
  • 理解 Rust 中的线程安全 RwLock> 机制

    背景 I am 全新的Rust 昨天开始 我试图确保我理解正确 我正在寻找为 游戏 编写一个配置系统 并希望它能够快速访问但偶尔可变 首先 我想研究本地化 这似乎是静态配置的合理用例 因为我意识到这些东西通常不会 生锈 我想出了以下 工作