在实际开发过程中,我们可能会遇到并发写文件的场景,如果处理不当很可能出现文件内容乱序问题。下面我们通过一个示例程序描述这一过程并给出解决该问题的方法。

use std::{
fs::{self, File, OpenOptions},
io::{Write},
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
use tokio::task::JoinSet; fn main() {
println!("parallel write file!");
let max_tasks = 200;
let _ = fs::remove_file("/tmp/parallel");
let file_ref = OpenOptions::new()
.create(true)
.write(true)
.append(true)
.open("/tmp/parallel")
.unwrap(); let mut set: JoinSet<()> = JoinSet::new();
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
loop {
while set.len() >= max_tasks {
set.join_next().await;
}
未做写互斥函数
let mut file_ref = OpenOptions::new()
.create(true)
.write(true)
.append(true)
.open("/tmp/parallel")
.unwrap();
set.spawn(async move { write_line(&mut file_ref) });
}
});
} fn write_line(file: &mut File) {
for i in 0..1000 {
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let mut content = now.as_secs().to_string();
content.push_str("_");
content.push_str(&i.to_string()); file.write_all(content.as_bytes()).unwrap();
file.write_all("\n".as_bytes()).unwrap();
file.write_all("\n".as_bytes()).unwrap();
}
}

代码不复杂,tokio 实现一个并发runtime,写文件函数是直接写时间戳,为了方便展示乱序所以写入两次换行。

输出的文本大概长这样

1691287258_979

1691287258_7931691287258_301

1691287258_7431691287258_603

1691287258_8941691287258_47

1691287258_895
1691287258_553 1691287258_950
1691287258_980 1691287258_48
1691287258_302 1691287258_896
1691287258_744 1691287258_6041691287258_554

很明显,写入并未达到预期,间隔并不平均,函数内部的执行步骤是乱序的。

我们把上面的程序改造一下

use std::{
fs::{self, File, OpenOptions},
io::Write,
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
use tokio::sync::Mutex;
use tokio::task::JoinSet; fn main() {
println!("parallel write file!");
let max_tasks = 200;
let _ = fs::remove_file("/tmp/parallel");
let file_ref = OpenOptions::new()
.create(true)
.write(true)
.append(true)
.open("/tmp/parallel")
.unwrap(); let f = Arc::new(Mutex::new(file_ref)); let mut set: JoinSet<()> = JoinSet::new();
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
loop {
while set.len() >= max_tasks {
set.join_next().await;
} let mut file = Arc::clone(&f);
set.spawn(async move { write_line_mutex(&mut file).await });
}
});
} async fn write_line_mutex(mutex_file: &Arc<Mutex<File>>) {
for i in 0..1000 {
let mut f = mutex_file.lock().await;
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
let mut content = now.as_secs().to_string();
content.push_str("_");
content.push_str(&i.to_string()); f.write_all(content.as_bytes()).unwrap();
f.write_all("\n".as_bytes()).unwrap();
f.write_all("\n".as_bytes()).unwrap();
}
}

这次我们用到了tokio::sync::Mutex,write_line_mutex函数在每次执行写任务以前先获取文件互斥锁。

看看这次的文件内容

1691288040_374

1691288040_374

1691288040_374

1691288040_375

1691288040_374

1691288040_374

1691288040_374

1691288040_374

1691288040_374

1691288040_374

1691288040_374

1691288040_374

1691288040_374

1691288040_374

1691288040_375

1691288040_375

1691288040_374

1691288040_375

1691288040_375

1691288040_375

1691288040_375

1691288040_375

1691288040_375

1691288040_375

1691288040_375

1691288040_375

1691288040_375

写入的格式正确,保证每次函数写函数完整执行。

关于文件写互斥这点事儿,今儿就聊到这。

完整源码

作者:京东科技 贾世闻

来源:京东云开发者社区

文盘Rust -- Mutex解决并发写文件乱序问题的更多相关文章

  1. Python中,os.listdir遍历纯数字文件乱序如何解决

    Python中,os.listdir遍历纯数字文件乱序如何解决 日常跑深度学习视觉相关代码时,常常需要对数据集进行处理.许多图像文件名是利用纯数字递增的方式命名.通常所用的排序函数sort(),是按照 ...

  2. Python3并发写文件

    使用python2在进行并发写的时候,发现文件会乱掉,就是某一行中间会插入其他行的内容. 但是在使用python3进行并发写的时候,无论是多进程,还是多线程,都没有出现这个问题,难道是python3的 ...

  3. 文盘Rust -- 把程序作为守护进程启动

    当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服.今天我们就来聊聊这个事儿. 最早大家部署应用的通常操作是 "nohu ...

  4. 文盘Rust -- 本地库引发的依赖冲突

    作者:京东科技 贾世闻 问题描述 clickhouse 的原生 rust 客户端目前比较好的有两个clickhouse-rs 和 clickhouse.rs .clickhouse-rs 是 tcp ...

  5. 文盘Rust -- struct 中的生命周期

    最近在用rust 写一个redis的数据校验工具.redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程.在开发中,不免要定义struct 中的 ...

  6. 文盘Rust -- 给程序加个日志

    作者:贾世闻 日志是应用程序的重要组成部分.无论是服务端程序还是客户端程序都需要日志做为错误输出或者业务记录.在这篇文章中,我们结合[log4rs](https://github.com/estk/l ...

  7. 文盘Rust -- rust 连接云上数仓 starwift

    作者:京东云 贾世闻 最近想看看 rust 如何集成 clickhouse,又犯了好吃懒做的心理(不想自己建环境),刚好京东云发布了兼容ck 的云原生数仓 Starwfit,于是搞了个实例折腾一番. ...

  8. 文盘Rust -- 用Tokio实现简易任务池

    作者:京东科技 贾世闻 Tokio 无疑是 Rust 世界中最优秀的异步Runtime实现.非阻塞的特性带来了优异的性能,但是在实际的开发中我们往往需要在某些情况下阻塞任务来实现某些功能. 我们看看下 ...

  9. 解决靶机Bee-Box 键盘乱序问题

    Bee-Box介绍 Bee-box官方称呼BWAPP,buggy web Application 这是一个集成了各种常见漏洞和最新漏洞的开源Web应用程序,目的是帮助网络安全爱好者.开发人员和学生发现 ...

  10. 用读写锁三句代码解决多线程并发写入文件 z

    C#使用读写锁三句代码简单解决多线程并发写入文件时提示“文件正在由另一进程使用,因此该进程无法访问此文件”的问题 在开发程序的过程中,难免少不了写入错误日志这个关键功能.实现这个功能,可以选择使用第三 ...

随机推荐

  1. 2022-04-26:给定一个数组componets,长度为A, componets[i] = j,代表i类型的任务需要耗时j 给定一个二维数组orders,长度为M, orders[i][0]代表i

    2022-04-26:给定一个数组componets,长度为A, componets[i] = j,代表i类型的任务需要耗时j 给定一个二维数组orders,长度为M, orders[i][0]代表i ...

  2. 2023-01-02:某天,小美在玩一款游戏,游戏开始时,有n台机器, 每台机器都有一个能量水平,分别为a1、a2、…、an, 小美每次操作可以选其中的一台机器,假设选的是第i台, 那小美可以将其变成

    2023-01-02:某天,小美在玩一款游戏,游戏开始时,有n台机器, 每台机器都有一个能量水平,分别为a1.a2.-.an, 小美每次操作可以选其中的一台机器,假设选的是第i台, 那小美可以将其变成 ...

  3. 使用Cordova插件实现两个app之间的相互调用和通讯

    几年前使用Cordova 进行两个app之间的相互调用和通讯:当时也是几经折腾,今天把它整理出来,理一下思路,也方便有同样需求的朋友参考 一.require引入 plugin require(&quo ...

  4. odoo开发教程十五:仪表板

    仪表盘可以通过外部ID引用其他视图文件的内容,整合到一个界面进行显示. 一:建立仪表盘视图文件 views/session_board.xml: 通过外部id引入要展示的视图文件--定义仪表板form ...

  5. IntelliJ IDEA 最新激活码:2023、2022及以下版本通用(亲测有效)

    分享一下 IntelliJ IDEA 2023.1 最新激活注册码,破解教程如下,可免费永久激活,亲测有效,下面是详细文档哦~ 申明:本教程 IntelliJ IDEA 破解补丁.激活码均收集于网络, ...

  6. 【python基础】日常知识点整理

    [三种方法调用] 1. 类中的方法区分为普通方法(self),静态方法(@staticMenthod),类方法@classMenthod,隐式参数(cls) <1> 普通方法:第一个参数 ...

  7. 柏林噪声&幻想大陆地图生成

    序言 之前介绍过perlin噪声的实现,现在应用实践一下--程序化生成幻想大陆 这里使用的是perlin噪声倍频技术(也称分形噪声),详情传送门:柏林噪声算法 代码示例使用的是shadertoy的语法 ...

  8. python mitmproxy抓包库

    一.简介 mitmproxy是一款用Python编写的支持HTTP(S)的中间人代理工具.它可以拦截.查看.修改.重放和保存HTTP/HTTPS流量 ,支持命令行界面和图形界面,可用于安全测试.网络调 ...

  9. 手把手教你实战TDD

    1. 前言 领域驱动设计,测试驱动开发. 我们在<手把手教你落地DDD>一文中介绍了领域驱动设计(DDD)的落地实战,本文将对测试驱动开发(TDD)进行探讨,主要内容有:TDD基本理解.T ...

  10. C++面试八股文:std::vector和std::list,如何选择?

    某日二师兄参加XXX科技公司的C++工程师开发岗位第24面: 面试官:list用过吗? 二师兄:嗯,用过. 面试官:请讲一下list的实现原理. 二师兄:std::list被称为双向链表,和C中手写双 ...