diff --git a/book/contents/advance/concurrency-with-threads/sync1.md b/book/contents/advance/concurrency-with-threads/sync1.md index 9f5e68b0..05ffc1bb 100644 --- a/book/contents/advance/concurrency-with-threads/sync1.md +++ b/book/contents/advance/concurrency-with-threads/sync1.md @@ -248,7 +248,7 @@ fn main() { 在上面的描述中,我们用了"可能"二字,原因在于死锁在这段代码中不是必然发生的,总有一次运行你能看到最后一行打印输出。这是由于子线程的初始化顺序和执行速度并不确定,我们无法确定哪个线程中的锁先被执行,因此也无法确定两个线程对锁的具体使用顺序。 -但是,可以简单的说明下死锁发生的必然条件:线程1锁住了`mutex1`并且线程`2`锁住了`mutex2`,然后线程1试图去访问`mutex2`,同时线程`2`试图去访问`mutex1`,就会锁住。 因为线程2需要等待线程1释放`mutex1`后,才会释放`mutex2`,而与此同时,线程1需要等待线程2释放`mutex2`后才能释放`mutex1`,这种情况造成了两个线程都无法释放对方需要的锁,最终锁死。 +但是,可以简单的说明下死锁发生的必然条件:线程1锁住了`mutex1`并且线程`2`锁住了`mutex2`,然后线程1试图去访问`mutex2`,同时线程`2`试图去访问`mutex1`,就会死锁。 因为线程2需要等待线程1释放`mutex1`后,才会释放`mutex2`,而与此同时,线程1需要等待线程2释放`mutex2`后才能释放`mutex1`,这种情况造成了两个线程都无法释放对方需要的锁,最终死锁。 那么为何某些时候,死锁不会发生?原因很简单,线程2在线程1锁`mutex1`之前,就已经全部执行完了,随之线程2的`mutex2`和`mutex1`被全部释放,线程1对锁的获取将不再有竞争者。 同理,线程1若全部被执行完,那线程2也不会被锁,因此我们在线程1中间加一个睡眠,增加死锁发生的概率。如果你在线程2中同样的位置也增加一个睡眠,那死锁将必然发生! @@ -307,7 +307,7 @@ fn main() { } ``` -为了演示`try_lock`的作用,我们特定使用了之前必定会死锁的代码,并且将`lock`替换程`try_lock`,与之前的结果不同,这段代码将不会再有死锁发生: +为了演示`try_lock`的作用,我们特定使用了之前必定会死锁的代码,并且将`lock`替换成`try_lock`,与之前的结果不同,这段代码将不会再有死锁发生: ```console 线程 0 锁住了mutex1,接着准备去锁mutex2 ! 线程 1 锁住了mutex2, 准备去锁mutex1 @@ -396,7 +396,7 @@ Err("WouldBlock") 如果不是追求特别极致的性能,建议选择前者。 -## 用条件(Condvar)控制线程的同步 +## 用条件变量(Condvar)控制线程的同步 `Mutex`用于解决资源安全访问的问题,但是我们还需要一个手段来解决资源访问顺序的问题。而Rust考虑到了这一点,为我们提供了条件变量(Condition Variables),它经常和`Mutex`一起使用,可以让线程挂起,直到某个条件发生后再继续执行,其实`Condvar`我们在之前的多线程章节就已经见到过,现在再来看一个不同的例子: @@ -474,7 +474,7 @@ async fn main() { let mut join_handles = Vec::new(); for _ in 0..5 { - let permit = semaphore.clone().acquire_owned().await.unwrap(); + let permit = semaphore.clone().acquire_owned().await.unwrap(); join_handles.push(tokio::spawn(async move { // // 在这里执行任务...