查看原文
其他

不易察觉的陷阱——Rust中的锁

秋风不度镇南关 码农真经 2023-12-25

最近写了个小工具,用到了锁来对共享资源实行互斥访问,无奈总是出现一些莫名奇妙的问题。

为了弄清楚原因,写了个小的测试程序来排查,结果让我对Rust中的锁有了更深的认识。

以下是一个简单的例子, 两个线程共享一个锁

fn main() {
let lock = Arc::new(Mutex::new(()));
let l1 = lock.clone();
let l2 = lock.clone();
std::thread::spawn(move ||{
println("thread 1 accure begin run");
l1.lock().unwrap();
println("thread 1 accure lock success..");
std::thread::sleep(Duration::from_secs(5));
println("thread 1 release lock..");
});
std::thread::sleep(Duration::from_secs(1));
std::thread::spawn(move ||{
println("thread 2 begin run");
l2.lock().unwrap();
println("thread 2 accure lock success..");
std::thread::sleep(Duration::from_secs(3));
println("thread 2 release lock..");
});
std::thread::sleep(Duration::from_secs(11));
println("main thread done");
}

fn println(msg: &str){
println!("{} {}", Local::now().format("%Y-%m-%d %H:%M:%S").to_string(), msg);
}

这段代码看起来没啥问题,应该可以实现互斥访问,也就是线程2只有在线程1释放锁后,才会拿到锁,然而结果确出乎我意料,以下是输出:

2022-03-19 13:04:25 thread 1 accure begin run
2022-03-19 13:04:25 thread 1 accure lock success..
2022-03-19 13:04:26 thread 2 begin run
2022-03-19 13:04:26 thread 2 accure lock success..
2022-03-19 13:04:29 thread 2 release lock..
2022-03-19 13:04:30 thread 1 release lock..
2022-03-19 13:04:37 main thread done

可以看到线程2在线程1 "持有" 锁时,仍然能够 "拿到" 锁, 不应该呀,冥思苦想了一阵后,想起了 Rust中,变量的生命周期问题,rust的变量出了作用域,就会自动销毁。

而锁也应该遵循这个原则,上面的代码中,我并未使用变量接收锁对象,只是简单调用

l1.lock().unwrap(); //这里获取锁对象后,并未用变量接收

所以可能是这个原因导致获取锁对象后,该对象里面就被回收了,所以锁没有成功。

带着这个猜测改写了代码

fn main() {
let lock = Arc::new(Mutex::new(()));
let l1 = lock.clone();
let l2 = lock.clone();
std::thread::spawn(move ||{
println("thread 1 accure begin run");
let v = l1.lock().unwrap();
println("thread 1 accure lock success..");
std::thread::sleep(Duration::from_secs(5));
println("thread 1 release lock..");
});
std::thread::sleep(Duration::from_secs(1));
std::thread::spawn(move ||{
println("thread 2 begin run");
let v = l2.lock().unwrap();
println("thread 2 accure lock success..");
std::thread::sleep(Duration::from_secs(3));
println("thread 2 release lock..");
});
std::thread::sleep(Duration::from_secs(11));
println("main thread done");
}

fn println(msg: &str){
println!("{} {}", Local::now().format("%Y-%m-%d %H:%M:%S").to_string(), msg);
}

果不其然,猜测是对的,输出如下

2022-03-19 13:18:32 thread 1 accure begin run
2022-03-19 13:18:32 thread 1 accure lock success..
2022-03-19 13:18:33 thread 2 begin run
2022-03-19 13:18:37 thread 1 release lock..
2022-03-19 13:18:37 thread 2 accure lock success..
2022-03-19 13:18:40 thread 2 release lock..
2022-03-19 13:18:44 main thread done

改了代码后,锁对象 v的生命周期直到代码块结束,只有线程1执行完代码块后,v才会被销毁,锁也才会释放。

注意到上面的v,并没有使用,能不能用匿名变量接收呢,如下

let _ = l1.lock().unwrap();

改了代码, 运行后

2022-03-19 13:22:52 thread 1 accure begin run
2022-03-19 13:22:52 thread 1 accure lock success..
2022-03-19 13:22:53 thread 2 begin run
2022-03-19 13:22:53 thread 2 accure lock success..
2022-03-19 13:22:56 thread 2 release lock..
2022-03-19 13:22:57 thread 1 release lock..
2022-03-19 13:23:04 main thread done

结果,锁再次失效,看来是不能用匿名变量来接收锁对象了。

总结
  1. 锁对象必须用变量接收,否则锁会立即被销毁,锁也就失效了

  2. 锁对象不能用匿名变量 '_' 接收, Rust中匿名变量也是声明后立马drop掉,这
    样锁也会失效

  3. 综合#1,#2 锁必须要按以下格式使用

let lock_obj = mylock.lock().unwrap()
.... you code


往期推荐

欢迎关注我的公众号“码中人”,原创技术文章第一时间推送。



继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存