【问题标题】:How can I use hyper::client from another thread?如何从另一个线程使用 hyper::client?
【发布时间】:2017-07-20 21:08:32
【问题描述】:

我有多个线程执行一些繁重的操作,我需要在工作中使用客户端。我使用 Hyper v0.11 作为 HTTP 客户端,我想重用连接,所以我需要共享相同的 hyper::Client 以保持打开连接(在 keep-alive 模式下)。

客户端不能在线程之间共享(它没有实现SyncSend)。这是我尝试做的代码的小sn-p:

let mut core = Core::new().expect("Create Client Event Loop");
let handle = core.handle();

let remote = core.remote();

let client = Client::new(&handle.clone());

thread::spawn(move || {

    // intensive operations...

    let response = &client.get("http://google.com".parse().unwrap()).and_then(|res| {
        println!("Response: {}", res.status());
        Ok(())
    });

    remote.clone().spawn(|_| {
        response.map(|_| { () }).map_err(|_| { () })
    });

    // more intensive operations...
});
core.run(futures::future::empty::<(), ()>()).unwrap();

此代码无法编译:

thread::spawn(move || {
^^^^^^^^^^^^^ within `[closure@src/load-balancer.rs:46:19: 56:6 client:hyper::Client<hyper::client::HttpConnector>, remote:std::sync::Arc<tokio_core::reactor::Remote>]`, the trait `std::marker::Send` is not implemented for `std::rc::Weak<std::cell::RefCell<tokio_core::reactor::Inner>>`

thread::spawn(move || {
^^^^^^^^^^^^^ within `[closure@src/load-balancer.rs:46:19: 56:6 client:hyper::Client<hyper::client::HttpConnector>, remote:std::sync::Arc<tokio_core::reactor::Remote>]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<std::cell::RefCell<hyper::client::pool::PoolInner<tokio_proto::util::client_proxy::ClientProxy<tokio_proto::streaming::message::Message<hyper::http::MessageHead<hyper::http::RequestLine>, hyper::Body>, tokio_proto::streaming::message::Message<hyper::http::MessageHead<hyper::http::RawStatus>, tokio_proto::streaming::body::Body<hyper::Chunk, hyper::Error>>, hyper::Error>>>>`
...
remote.clone().spawn(|_| {
               ^^^^^ the trait `std::marker::Sync` is not implemented for `futures::Future<Error=hyper::Error, Item=hyper::Response> + 'static`

有没有办法从不同的线程或其他方法重用同一个客户端?

【问题讨论】:

    标签: multithreading rust hyper rust-tokio


    【解决方案1】:

    简短的回答是否定的,但这样会更好。

    每个Client 对象都拥有一个连接池。以下是 Hyper 的 Pool 在 0.11.0 版本中的定义:

    pub struct Pool<T> {
        inner: Rc<RefCell<PoolInner<T>>>,
    }
    

    由于inner 使用Rc 进行引用计数并在运行时使用RefCell 进行借用检查,因此池肯定不是线程安全的。当您尝试将 Client 移动到新线程时,该对象将持有一个存在于另一个线程中的池,这将成为数据竞争的来源。

    这个实现是可以理解的。尝试跨多个线程重用 HTTP 连接并不常见,因为它需要对主要是 I/O 密集型资源的同步访问。这与 Tokio 的异步特性非常吻合。在同一个线程中执行多个请求实际上更合理,让 Tokio 的核心负责异步发送和接收消息,而不用依次等待每个响应。此外,计算密集型任务可以由来自futures_cpupool 的 CPU 池执行。考虑到这一点,下面的代码可以正常工作:

    extern crate tokio_core;
    extern crate hyper;
    extern crate futures;
    extern crate futures_cpupool;
    
    use tokio_core::reactor::Core;
    use hyper::client::Client;
    use futures::Future;
    use futures_cpupool::CpuPool;
    
    fn main() {
    
        let mut core = Core::new().unwrap();
        let handle = core.handle();
        let client = Client::new(&handle.clone());
        let pool = CpuPool::new(1);
    
        println!("Begin!");
        let req = client.get("http://google.com".parse().unwrap())
            .and_then(|res| {
                println!("Response: {}", res.status());
                Ok(())
            });
        let intensive = pool.spawn_fn(|| {
            println!("I'm working hard!!!");
            std::thread::sleep(std::time::Duration::from_secs(1));
            println!("Phew!");
            Ok(())
        });
    
        let task = req.join(intensive)
            .map(|_|{
                println!("End!");
            });
        core.run(task).unwrap();
    }
    

    如果没有收到响应太晚,输出将是:

    Begin!
    I'm working hard!!!
    Response: 302 Found
    Phew!
    End!
    

    如果您有多个任务在不同的线程中运行,那么问题就会变得开放,因为有多种架构是可行的。其中之一是将所有通信委托给单个参与者,因此需要所有其他工作线程将其数据发送给它。或者,您可以为每个工作人员拥有一个客户端对象,从而也可以拥有单独的连接池。

    【讨论】:

    • 感谢您的回复!!我不同意重用来自不同线程的连接是不可用的这一事实,我说,如果你有一个基于期货的模式(类似于带有 Akka HTTP 的 Scala),你可以持有一堆期货,其中一些可以取决于请求。如果你在同一个事件循环中,这在 tokio 中效果很好,但是你没有简单的方法让多个线程将请求 http 等任务提交到外部事件循环。
    • 你还没有描述你的许多线程会做什么。到目前为止,这听起来像是一个 XY 问题。您是否考虑过将这些多线程任务的结果转移到客户端的线程,而不是反过来?
    • 是的,没错,这就是用例:我正在构建一个简约的负载均衡器,带有一些加密和解密操作(主要是密集操作),我认为的第一种方法是有一个事件循环来接收连接,一个线程池来处理连接和n个事件循环作为HTTP客户端。我发现它很难实现,所以现在我只有 N 个事件循环,每个都接收 tcp 连接,解密一个令牌。并执行一些请求。但我认为这不是更好的方法。
    • “接收连接的事件循环” 是一个 tokio 核心,那部分就可以了。如果没有一个新的、具体的问题,其余的听起来就无法在这里解决。就 Hyper 而言,Client 不是线程安全的,并且将其包裹在线程同步机制中简直是个坏主意。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多