Tokio实现I3状态栏生成器:循环执行与SIGUSR1触发可变函数
解决I3状态栏生成器的Tokio并发与可变借用冲突问题
核心问题分析
你遇到的可变变量借用冲突,本质是Rust的借用规则不允许同一时间存在多个&mut self引用。Tokio的join!会并发执行两个异步任务,主循环和信号监听任务都需要修改结构体状态,直接使用&mut self必然触发编译错误。
解决方案:用线程安全的内部可变性容器重构
通过Arc<Mutex>(或Arc<RwLock>)包装需要共享修改的状态,实现多任务下的安全访问,同时符合Tokio的异步模型。
步骤1:调整结构体定义
将可变字段用Arc<Mutex>包裹,实现线程安全的共享所有权与内部可变性:
use std::sync::{Arc, Mutex}; use tokio::signal::unix::{signal, SignalKind}; use tokio::time::{sleep, Instant, Duration}; // 假设Plug是你的插件 trait pub trait Plug { fn prefix(&mut self) -> String; fn plug_out(&mut self) -> String; } pub struct BarGenerator { speed: u64, is_powerline: bool, end_pw_line: bool, fg: String, bg: String, seperator: String, pw_colors: Vec<String>, pw_color_num: Arc<Mutex<usize>>, // 包装可变状态 function_list: Arc<Mutex<Vec<Box<dyn Plug>>>>, // 包装可变插件列表 }
步骤2:重构run_bar方法
拆分异步任务,用锁安全访问共享状态,同时运行主循环与SIGUSR1监听:
pub async fn run_bar(self) { let interval = Duration::from_millis(self.speed); // 捕获不可变状态 let pw_colors = self.pw_colors; let is_powerline = self.is_powerline; let end_pw_line = self.end_pw_line; let fg = self.fg; let bg = self.bg; let seperator = self.seperator; // 捕获锁包裹的可变状态 let pw_color_num = self.pw_color_num; let function_list = self.function_list; // 提取状态栏生成逻辑,通过锁访问可变状态 let generate_status = move || { let mut statusline = String::new(); // 获取锁访问pw_color_num let mut pw_num_guard = pw_color_num.lock().unwrap(); let mut pw_color_num_val = *pw_num_guard; let og_pw_num = pw_color_num_val; // 获取锁访问插件列表 let mut funcs_guard = function_list.lock().unwrap(); let fn_list_size = funcs_guard.len(); for (i, x) in funcs_guard.iter_mut().enumerate() { let og_pw_num_2 = pw_color_num_val; if pw_color_num_val == pw_colors.len() { pw_color_num_val = og_pw_num; } if is_powerline { if i == 0 { statusline.push_str(&format!( "<span foreground='{}'></span>", pw_colors[pw_color_num_val] )); } else if pw_color_num_val != 0 { statusline.push_str(&format!( "<span background='{}' foreground='{}'></span>", pw_colors[pw_color_num_val], pw_colors[pw_color_num_val - 1] )); } else { statusline.push_str(&format!( "<span background='{}' foreground='{}'></span>", pw_colors[pw_color_num_val], pw_colors[og_pw_num_2 - 1] )); } statusline.push_str(&format!( "<span background='{}' foreground='{}'> ", pw_colors[pw_color_num_val], fg )); } statusline.push_str(x.prefix().as_str()); statusline.push_str(x.plug_out().as_str()); if is_powerline { statusline.push_str(" </span>"); if i == fn_list_size - 1 && end_pw_line { statusline.push_str(&format!( "<span foreground='{}' background='{}'></span>", pw_colors[pw_color_num_val], bg )) } pw_color_num_val += 1; } if i != fn_list_size - 1 && !is_powerline { statusline.push_str(&seperator) } } // 更新锁中的pw_color_num值 *pw_num_guard = pw_color_num_val; println!("{}", statusline); }; // 主循环任务:定时生成状态栏 let main_loop = async move { let mut next_time = Instant::now() + interval; loop { generate_status(); sleep(next_time - Instant::now()).await; next_time += interval; } }; // SIGUSR1监听任务:收到信号立即更新状态栏 let sigusr1_listener = async move { let mut sigusr1 = signal(SignalKind::user_defined1()).unwrap(); loop { match sigusr1.recv().await { Some(()) => generate_status(), None => eprintln!("SIGUSR1信号流终止"), } } }; // 并发执行两个任务 tokio::join!(main_loop, sigusr1_listener); }
关键修改说明
- 内部可变性容器:
Arc<Mutex>允许在多个异步任务中安全共享和修改状态,Arc提供线程安全的所有权共享,Mutex保证同一时间只有一个任务能修改内部数据。 - 任务拆分:将主循环与信号监听拆分为独立异步任务,通过
tokio::join!并发执行,符合Tokio异步调度模型。 - 锁的使用:每次生成状态栏时,通过
lock()获取状态的访问权限,用完自动释放锁,避免长期阻塞。
可选优化
如果插件列表的读操作远多于写操作,可以用Arc<RwLock>替代Arc<Mutex>,允许多个任务同时读取状态,提升并发性能。
内容的提问来源于stack exchange,提问作者tux7k
相关产品推荐
相关产品推荐

