Rust中子线程疑似被主线程阻塞,无界通道无限增长
背景
我在开发第一个正式Rust程序,功能是从FPGA内存(CentOS系统下FPGA配套驱动生成的二进制文件)读取数据并写入HDF5文件。FPGA以400Hz(每2.5ms)的频率输出10个512×16bit的向量。
最初遇到HDF5写入平均耗时约1ms,但偶尔会长达15ms,导致数据丢失。于是用Rust的mpsc实现读写异步分离,只要写入操作的平均耗时低于2.5ms,就能避免数据丢失。
开发阶段用while loop_start.elapsed().as_micros() < MAIN_SLEEP_TIME {}模拟400Hz的更新速率(loop_start是std::time::Instant::now()的实例),后续会替换为轮询FPGA内存的递增计数器或中断逻辑。
当前问题
主线程中的延时操作似乎阻塞了写线程,写线程的循环耗时始终接近MAIN_SLEEP_TIME,导致线程间的无界通道因数据写入速度远快于读取速度而持续膨胀,最终程序因内存耗尽崩溃。
复现代码
use std::{thread, time}; use std::sync::mpsc::{channel, Receiver}; use std::io::prelude::*; use std::fs::File; const MAIN_SLEEP_TIME: u128 = 2500; // 延时微秒数 #[derive(Debug)] struct DataContainer{ internal_count: u64, fake_vector: [u8; 10240] } fn main() { let mut loop_counter: u64 = 0; let mut fake_counter: u8 = 0; let (datasender, datareceiver) = channel::<DataContainer>(); // 该无界通道会无限增长,最终触发OOM错误 let mut data = DataContainer { internal_count: 0, fake_vector: [127; 10240] }; let write_thread = thread::spawn(|| { write_thread(datareceiver); }); loop { let loop_start = time::Instant::now(); data = DataContainer{ internal_count: loop_counter, fake_vector: [fake_counter; 10240] }; loop_counter += 1; fake_counter += 1; datasender.send(data); while loop_start.elapsed().as_micros() < MAIN_SLEEP_TIME { // 此循环会导致write_thread的循环耗时始终接近MAIN_SLEEP_TIME // 替换为thread::sleep(time::Duration::from_micros(MAIN_SLEEP_TIME))问题依旧 } } } fn write_thread(receiver: Receiver<DataContainer>) { let mut write_counter = 0; let mut file = File::create("output.bin").unwrap(); loop { let now = time::Instant::now(); let received_data = receiver .recv_timeout(time::Duration::from_millis(50)).unwrap(); file.write_all(&received_data.fake_vector); println!("Wrote loop {}! This took {} us!", write_counter, now.elapsed().as_micros()); // 输出会类似"Wrote loop [n]! This took [~MAIN_SLEEP_TIME] us!" write_counter += 1; } }
输出示例
当MAIN_SLEEP_TIME=2500时:
Wrote loop 0! This took 60 us! Wrote loop 1! This took 2298 us! Wrote loop 2! This took 2420 us! Wrote loop 3! This took 2487 us! Wrote loop 4! This took 2538 us! Wrote loop 5! This took 2462 us! Wrote loop 6! This took 2471 us! Wrote loop 7! This took 2484 us! Wrote loop 8! This took 2507 us! Wrote loop 9! This took 2468 us! Wrote loop 10! This took 2489 us! Wrote loop 11! This took 2461 us! Wrote loop 12! This took 2492 us! Wrote loop 13! This took 2488 us! Wrote loop 14! This took 2520 us! Wrote loop 15! This took 2471 us! Wrote loop 16! This took 2494 us! Wrote loop 17! This took 2470 us! Wrote loop 18! This took 2483 us! Wrote loop 19! This took 2487 us! Wrote loop 20! This took 2490 us!
当MAIN_SLEEP_TIME=5000时:
Wrote loop 0! This took 50 us! Wrote loop 1! This took 5066 us! Wrote loop 2! This took 5989 us! Wrote loop 3! This took 5956 us! Wrote loop 4! This took 5972 us! Wrote loop 5! This took 5987 us! Wrote loop 6! This took 5999 us! Wrote loop 7! This took 5977 us! Wrote loop 8! This took 5999 us! Wrote loop 9! This took 5982 us! Wrote loop 10! This took 5990 us! Wrote loop 11! This took 6009 us! Wrote loop 12! This took 6008 us! Wrote loop 13! This took 5975 us! Wrote loop 14! This took 5953 us! Wrote loop 15! This took 5991 us! Wrote loop 16! This took 5995 us! Wrote loop 17! This took 5969 us! Wrote loop 18! This took 5984 us! Wrote loop 19! This took 5987 us! Wrote loop 20! This took 5989 us!
问题分析与解决方案
根本原因
主线程的忙等待循环会占用CPU核心的全部时间片,操作系统调度器几乎不会给写线程分配运行时间;即使改用thread::sleep,默认调度策略也可能让主线程优先抢占CPU,导致写线程无法及时处理通道内的数据,最终通道持续膨胀引发OOM。
解决办法
- 给忙等待循环添加CPU让步
在忙等待的循环中加入thread::yield_now(),主动让出CPU给其他线程,让写线程获得运行机会:
while loop_start.elapsed().as_micros() < MAIN_SLEEP_TIME { thread::yield_now(); // 主动让渡CPU资源 }
- 改用有界通道限制内存增长
使用mpsc::sync_channel创建有界通道,当通道满时主线程会自动阻塞,避免数据无限堆积:
// 设置通道容量为10,可根据实际场景调整 let (datasender, datareceiver) = sync_channel::<DataContainer>(10);
优化文件写入逻辑
当前同步写入磁盘的方式会占用大量时间,可改用批量写入或异步IO库(如tokio+tokio-fs)提升写线程处理速度,减少IO等待时间。调整线程调度优先级
在Linux系统下,可通过libc库调用setpriority给写线程设置更高的调度优先级,确保写线程能及时被调度执行。
内容的提问来源于stack exchange,提问作者hso

