首页 > 解决方案 > 为什么 Rust 互斥锁似乎没有将锁给最后想要锁定它的线程?

问题描述

我想编写一个程序,它产生两个线程来锁定 a Mutex,增加它,打印一些东西,然后解锁,Mutex以便另一个线程可以做同样的事情。我添加了一些睡眠时间以使其更加一致,所以我认为输出应该是这样的:

ping pong ping pong …  

但实际输出是相当随机的。大多数时候,它只是

ping ping ping … pong 

但是根本没有一致性。有时中间也有一个“乒乓”。

我相信互斥体有某种方法可以确定谁想最后锁定它,但看起来情况并非如此。

  1. 锁定实际上是如何工作的?
  2. 如何获得所需的输出?
use std::sync::{Arc, Mutex};
use std::{thread, time};

fn main() {
    let data1 = Arc::new(Mutex::new(1));
    let data2 = data1.clone();
    let ten_millis = time::Duration::from_millis(10);

    let a = thread::spawn(move || loop {
        let mut data = data1.lock().unwrap();
        thread::sleep(ten_millis);
        println!("ping ");
        *data += 1;
        if *data > 10 {
            break;
        }
    });

    let b = thread::spawn(move || loop {
        let mut data = data2.lock().unwrap();
        thread::sleep(ten_millis);
        println!("pong ");
        *data += 1;
        if *data > 10 {
            break;
        }
    });

    a.join().unwrap();
    b.join().unwrap();
}

标签: multithreadingrustmutexshared-memory

解决方案


Mutex并且RwLock两者都遵循特定于操作系统的原语,并且不能保证是公平的。在 Windows 上,它们都使用SRW 锁实现,这些锁被明确记录为公平。我没有对其他操作系统进行研究,但你绝对不能依赖公平性std::sync::Mutex,特别是如果你需要这个代码是可移植的。

Rust 中一个可能的解决方案是 crateMutex提供的实现,它提供了一个方法,该方法是用公平算法实现的。parking_lotunlock_fair

parking_lot文档中:

默认情况下,互斥锁是不公平的,它允许当前线程在另一个线程有机会获得锁之前重新锁定互斥锁,即使该线程已经在互斥锁上阻塞了很长时间。这是默认设置,因为它避免了在每个互斥锁解锁时强制进行上下文切换,从而允许更高的吞吐量。这可能导致一个线程比其他线程获取互斥锁的次数更多。

但是,在某些情况下,如果有一个等待线程,则强制将锁传递给等待线程,从而确保公平性可能是有益的。这是通过使用此方法而不是MutexGuard正常删除来完成的。

虽然parking_lot::Mutex没有专门使用该unlock_fair方法并不声称是公平的,但我发现您的代码仅通过进行该开关(操场)产生与乒乓球相同数量的 ping,甚至不使用该unlock_fair方法。

通常,当守卫超出范围时,互斥锁会自动解锁。为了让它公平地解锁,你需要在防护被丢弃之前插入这个方法调用:

let b = thread::spawn(move || loop {
    let mut data = data1.lock();
    thread::sleep(ten_millis);
    println!("pong ");
    *data += 1;
    if *data > 10 {
        break;
    }
    MutexGuard::unlock_fair(data);
});

推荐阅读