如何避免线程恐慌造成的死锁?

问题描述 投票:1回答:1

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

现在假设服务器发生恐慌 - 例如尝试将套接字绑定到端口80.客户端将永远留在wait()-ing。我们不能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);
}
rust synchronization deadlock
1个回答
2
投票

而不是Barrier我会在这里使用Condvar

为了真正解决您的问题,我发现至少有三种可能的解决方案:

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

我认为应该优先考虑解决方案1或2而不是第三个,因为你将避免确保另一个线程首先锁定了Mutex

© www.soinside.com 2019 - 2024. All rights reserved.