You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现不同I²C总线实例的多线程并发访问?

问题

现有一段Rust代码用于管理I²C总线操作,但由于全局HashMap外层包裹了Mutex,导致不同I²C总线无法并发访问,如何修改代码实现不同总线的并发访问?

原代码如下:

use once_cell::sync::Lazy;
use rppal::i2c::{Error, I2c};
use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
};

static GLOBAL_I2C: Lazy<Arc<Mutex<HashMap<u8, I2c>>>> =
    Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));

fn perform_i2c_operation<T, F>(
    bus: u8,
    slave_address: u16,
    operation: F,
) -> Result<T, Error>
where
    F: FnOnce(&mut I2c) -> Result<T, Error>,
{
    // Get the mutex lock for the HashMap
    let mut i2c_map = match GLOBAL_I2C.lock() {
        Ok(i2c_map) => i2c_map,
        Err(e) => {
            return Err(Error::Io(std::io::Error::new(
                std::io::ErrorKind::Other,
                "Not really a thread panic.",
            )));
        }
    };

    // Check if the bus we're interested in is in the map, if not, insert it.
    if !i2c_map.contains_key(&bus) {
        match initialize_i2c(bus) {
            Ok(i2c) => {
                i2c_map.insert(bus, i2c);
            }
            Err(e) => {
                return Err(Error::Io(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    format!("I²C bus couldn't be initialized: {}.", e),
                )));
            }
        }
    }

    // Retrieve the i2c bus instance and perform the operations on it.
    match i2c_map.get_mut(&bus) {
        Some(i2c_instance) => {
            match i2c_instance.set_slave_address(slave_address) {
                Ok(()) => {             
                    operation(i2c_instance)
                }
                Err(e) => Err(e),
            }        
        }
        None => {
            return Err(Error::Io(std::io::Error::new(
                std::io::ErrorKind::Other,
                "I²C bus couldn't be initialized:.")));
        }
    }
}
解决方案

核心思路是缩小锁的粒度——不给整个HashMap加锁,而是给每个I²C总线实例单独加锁。这样不同总线的操作可以同时进行,只有同一条总线的操作会互斥。

具体修改步骤:

  1. 调整全局HashMap的类型,让每个总线实例被Arc<Mutex>包裹,类型变为Arc<Mutex<HashMap<u8, Arc<Mutex<I2c>>>>>。
  2. 拆分锁的持有逻辑:先短暂锁定HashMap查找/初始化总线,拿到对应总线的Arc<Mutex<I2c>>后立即释放HashMap的锁,再单独锁定该总线实例执行操作。

修改后的代码如下:

use once_cell::sync::Lazy;
use rppal::i2c::{Error, I2c};
use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
};

// 全局HashMap存储带锁的I²C总线实例
static GLOBAL_I2C: Lazy<Arc<Mutex<HashMap<u8, Arc<Mutex<I2c>>>>>> =
    Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));

fn perform_i2c_operation<T, F>(
    bus: u8,
    slave_address: u16,
    operation: F,
) -> Result<T, Error>
where
    F: FnOnce(&mut I2c) -> Result<T, Error>,
{
    // 第一步:锁定HashMap,查找或初始化目标总线
    let i2c_mutex = {
        let mut i2c_map = match GLOBAL_I2C.lock() {
            Ok(map) => map,
            Err(_) => {
                return Err(Error::Io(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    "Mutex poisoned",
                )));
            }
        };

        // 初始化未存在的总线
        if !i2c_map.contains_key(&bus) {
            let i2c = initialize_i2c(bus)?;
            i2c_map.insert(bus, Arc::new(Mutex::new(i2c)));
        }

        // 克隆Arc,后续释放HashMap的锁后仍能操作总线
        i2c_map.get(&bus).unwrap().clone()
    }; // HashMap的锁在此处自动释放

    // 第二步:单独锁定目标总线,执行操作
    let mut i2c_instance = match i2c_mutex.lock() {
        Ok(instance) => instance,
        Err(_) => {
            return Err(Error::Io(std::io::Error::new(
                std::io::ErrorKind::Other,
                "I2C bus mutex poisoned",
            )));
        }
    };

    // 设置从设备地址并执行自定义操作
    i2c_instance.set_slave_address(slave_address)?;
    operation(&mut i2c_instance)
}

// 保留原初始化函数(示例实现)
fn initialize_i2c(bus: u8) -> Result<I2c, Error> {
    I2c::new(bus)
}

关键改进点

  • 锁粒度拆分:HashMap的锁仅用于总线的查找和初始化,操作总线时只持有对应总线的锁,不同总线的操作完全并发。
  • 资源复用:通过Arc实现总线实例的多线程共享,避免重复初始化硬件资源。
  • 代码简化:使用?操作符替代嵌套match,提升代码可读性和简洁性。

内容的提问来源于stack exchange,提问作者Jamie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 17:37:17