文盘Rust -- Mutex解决并发写文件乱序问题

打印 上一主题 下一主题

主题 937|帖子 937|积分 2811

在实际开发过程中,我们可能会遇到并发写文件的场景,如果处理不当很可能出现文件内容乱序问题。下面我们通过一个示例程序描述这一过程并给出解决该问题的方法。
  1. use std::{
  2.     fs::{self, File, OpenOptions},
  3.     io::{Write},
  4.     sync::Arc,
  5.     time::{SystemTime, UNIX_EPOCH},
  6. };
  7. use tokio::task::JoinSet;
  8. fn main() {
  9.     println!("parallel write file!");
  10.     let max_tasks = 200;
  11.     let _ = fs::remove_file("/tmp/parallel");
  12.     let file_ref = OpenOptions::new()
  13.         .create(true)
  14.         .write(true)
  15.         .append(true)
  16.         .open("/tmp/parallel")
  17.         .unwrap();
  18.     let mut set: JoinSet<()> = JoinSet::new();
  19.     let rt = tokio::runtime::Runtime::new().unwrap();
  20.     rt.block_on(async {
  21.         loop {
  22.             while set.len() >= max_tasks {
  23.                 set.join_next().await;
  24.             }
  25.             未做写互斥函数
  26.             let mut file_ref = OpenOptions::new()
  27.                 .create(true)
  28.                 .write(true)
  29.                 .append(true)
  30.                 .open("/tmp/parallel")
  31.                 .unwrap();
  32.             set.spawn(async move { write_line(&mut file_ref) });
  33.         }
  34.     });
  35. }
  36. fn write_line(file: &mut File) {
  37.     for i in 0..1000 {
  38.         let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
  39.         let mut content = now.as_secs().to_string();
  40.         content.push_str("_");
  41.         content.push_str(&i.to_string());
  42.         file.write_all(content.as_bytes()).unwrap();
  43.         file.write_all("\n".as_bytes()).unwrap();
  44.         file.write_all("\n".as_bytes()).unwrap();
  45.     }
  46. }
复制代码
代码不复杂,tokio 实现一个并发runtime,写文件函数是直接写时间戳,为了方便展示乱序所以写入两次换行。
输出的文本大概长这样
  1. 1691287258_979
  2. 1691287258_7931691287258_301
  3. 1691287258_7431691287258_603
  4. 1691287258_8941691287258_47
  5. 1691287258_895
  6. 1691287258_553
  7. 1691287258_950
  8. 1691287258_980
  9. 1691287258_48
  10. 1691287258_302
  11. 1691287258_896
  12. 1691287258_744
  13. 1691287258_6041691287258_554
复制代码
很明显,写入并未达到预期,间隔并不平均,函数内部的执行步骤是乱序的。
我们把上面的程序改造一下
  1. use std::{
  2.     fs::{self, File, OpenOptions},
  3.     io::Write,
  4.     sync::Arc,
  5.     time::{SystemTime, UNIX_EPOCH},
  6. };
  7. use tokio::sync::Mutex;
  8. use tokio::task::JoinSet;
  9. fn main() {
  10.     println!("parallel write file!");
  11.     let max_tasks = 200;
  12.     let _ = fs::remove_file("/tmp/parallel");
  13.     let file_ref = OpenOptions::new()
  14.         .create(true)
  15.         .write(true)
  16.         .append(true)
  17.         .open("/tmp/parallel")
  18.         .unwrap();
  19.     let f = Arc::new(Mutex::new(file_ref));
  20.     let mut set: JoinSet<()> = JoinSet::new();
  21.     let rt = tokio::runtime::Runtime::new().unwrap();
  22.     rt.block_on(async {
  23.         loop {
  24.             while set.len() >= max_tasks {
  25.                 set.join_next().await;
  26.             }
  27.             let mut file = Arc::clone(&f);
  28.             set.spawn(async move { write_line_mutex(&mut file).await });
  29.         }
  30.     });
  31. }
  32. async fn write_line_mutex(mutex_file: &Arc<Mutex<File>>) {
  33.     for i in 0..1000 {
  34.         let mut f = mutex_file.lock().await;
  35.         let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
  36.         let mut content = now.as_secs().to_string();
  37.         content.push_str("_");
  38.         content.push_str(&i.to_string());
  39.         f.write_all(content.as_bytes()).unwrap();
  40.         f.write_all("\n".as_bytes()).unwrap();
  41.         f.write_all("\n".as_bytes()).unwrap();
  42.     }
  43. }
复制代码
这次我们用到了tokio::sync::Mutex,write_line_mutex函数在每次执行写任务以前先获取文件互斥锁。
看看这次的文件内容
  1. 1691288040_374
  2. 1691288040_374
  3. 1691288040_374
  4. 1691288040_375
  5. 1691288040_374
  6. 1691288040_374
  7. 1691288040_374
  8. 1691288040_374
  9. 1691288040_374
  10. 1691288040_374
  11. 1691288040_374
  12. 1691288040_374
  13. 1691288040_374
  14. 1691288040_374
  15. 1691288040_375
  16. 1691288040_375
  17. 1691288040_374
  18. 1691288040_375
  19. 1691288040_375
  20. 1691288040_375
  21. 1691288040_375
  22. 1691288040_375
  23. 1691288040_375
  24. 1691288040_375
  25. 1691288040_375
  26. 1691288040_375
  27. 1691288040_375
复制代码
写入的格式正确,保证每次函数写函数完整执行。
关于文件写互斥这点事儿,今儿就聊到这。
完整源码
作者:京东科技 贾世闻
来源:京东云开发者社区

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

美食家大橙子

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表