如何避免由于线程崩溃而导致的死锁?

时间:2019-03-21 12:22:21

标签: rust synchronization deadlock

我的服务器在可以安全尝试连接时使用Barrier通知客户端。没有障碍,我们将冒随机失败的风险,因为无法保证服务器套接字将被绑定。

现在,假设服务器出现紧急情况-例如,尝试将套接字绑定到端口80。客户端将永远处于wait()状态。我们无法join()来确定服务器线程是否崩溃,因为join()是阻塞操作-如果我们join()无法connect()

假设std::sync API不向方法提供超时,那么执行这种同步的正确方法是什么?

这只是一个MCVE来演示此问题。我在单元测试中有一个类似的案例-它永远运行着。

use std::{
    io::prelude::*,
    net::{SocketAddr, TcpListener, TcpStream},
    sync::{Arc, Barrier},
};

fn main() {
    let port = 9090;
    //let port = 80;

    let barrier = Arc::new(Barrier::new(2));
    let server_barrier = barrier.clone();

    let client_sync = move || {
        barrier.wait();
    };

    let server_sync = Box::new(move || {
        server_barrier.wait();
    });

    server(server_sync, port);
    //server(Box::new(|| { no_sync() }), port); //use to test without synchronisation

    client(&client_sync, port);
    //client(&no_sync, port); //use to test without synchronisation
}

fn no_sync() {
    // do nothing in order to demonstrate the need for synchronization
}

fn server(sync: Box<Fn() + Send + Sync>, port: u16) {
    std::thread::spawn(move || {
        std::thread::sleep_ms(100); //there is no guarantee when the os will schedule the thread. make it 100% reproducible
        let addr = SocketAddr::from(([127, 0, 0, 1], port));
        let socket = TcpListener::bind(&addr).unwrap();
        println!("server socket bound");
        sync();

        let (mut client, _) = socket.accept().unwrap();

        client.write_all(b"hello mcve").unwrap();
    });
}

fn client(sync: &Fn(), port: u16) {
    sync();

    let addr = SocketAddr::from(([127, 0, 0, 1], port));
    let mut socket = TcpStream::connect(&addr).unwrap();
    println!("client socket connected");

    let mut buf = String::new();
    socket.read_to_string(&mut buf).unwrap();
    println!("client received: {}", buf);
}

1 个答案:

答案 0 :(得分:2)

在这里我将使用Condvar代替Barrier

要真正解决您的问题,我至少看到了三种可能的解决方案:

  1. 使用Condvar::wait_timeout并将超时设置为合理的持续时间(例如1秒,足以绑定到端口)
  2. 您可以使用与上述相同的方法,但是超时时间较短(例如10毫秒),并检查Mutex是否中毒。
  3. 您可以使用普通的Mutex来代替Condvar(确保Mutex首先被另一个线程锁定),然后使用Mutex::try_lock进行检查如果Mutex is poisoned

我认为与第三个解决方案相比,应该更喜欢解决方案1或2,因为您将避免确保另一个线程首先锁定了Mutex