使用tokio::sync::Semaphore
信号量可以保证在某一时刻最多运行指定数量的并发任务。
使用信号量时,需在初始化时指定信号灯(tokio中的SemaphorePermit)的数量,每当任务要执行时,将从中取走一个信号灯,当任务完成时(信号灯被drop)会归还信号灯。当某个任务要执行时,如果此时信号灯数量为0,则该任务将等待,直到有信号灯被归还。因此,信号量通常用来提供类似于限量的功能。
例如,信号灯数量为1,表示所有并发任务必须串行运行,这种模式和互斥锁是类似的。再例如,信号灯数量设置为2,表示最多只有两个任务可以并发执行,如果有第三个任务,则必须等前两个任务中的某一个先完成。
例如:
use chrono::Local;
use std::sync::Arc;
use tokio::{ self, runtime::Runtime, sync::Semaphore, time::{self, Duration}};
fn now() -> String {
Local::now().format("%F %T").to_string()
}
fn main() {
let rt = Runtime::new().unwrap();
rt.block_on(async {
// 只有3个信号灯的信号量
let semaphore = Arc::new(Semaphore::new(3));
// 5个并发任务,每个任务执行前都先获取信号灯
// 因此,同一时刻最多只有3个任务进行并发
for i in 1..=5 {
let semaphore = semaphore.clone();
tokio::spawn(async move {
let _permit = semaphore.acquire().await.unwrap();
println!("{}, {}", i, now());
time::sleep(Duration::from_secs(1)).await;
});
}
time::sleep(Duration::from_secs(3)).await;
});
}
互斥锁 Mutex实现
这是我在没有发现信号灯机制前用的方法,问的gpt,案例来自我自己的项目
let tasks = Arc::new(Mutex::new(0));
let mut handles = vec![];
for args in args_list {
let app_handle = app.app_handle();
let real_path_str_copy = real_path_str.clone();
let tasks_ref = Arc::clone(&tasks);
let handle = tokio::spawn(async move {
let mut guard = tasks_ref.lock().await;
while *guard >= max_tasks {
drop(guard);
task::yield_now().await;
guard = tasks_ref.lock().await;
}
*guard += 1;
drop(guard);
run_task(app_handle, &args, &real_path_str_copy)
.await
.unwrap();
let mut guard = tasks_ref.lock().await;
*guard -= 1;
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
嗯,复杂多了,用互斥锁来获取当前运行了多少task,当 *guard >= max_tasks成立时,使用 task::tield_now().await;来把当前task的控制权移交给tokio而不往下执行。
怕while判断时,guard已经被丢弃,还在控制权移交的下一行增加了获取锁,rust不是很熟练,不是很清楚这里有没有起作用,因为理论上每一次 while循环 guard都离开了作用域,也就是会被自动 drop丢弃。
不管怎么说,It just work,并发确实被控制在了 max_tasks以内。
async move的语法创建一个异步block,会自动将所有用到的外部变量的所有权移交到block内,所以需要在定于block前复制一份。