Actix: как правильно перемещать общие данные в несколько потоков?

У меня есть config Struct, которым я делюсь в своем приложении actix, например:

pub fn run(addr: &str, pg_pool: PgPool, config: Settings) -> Result<Server, std::io::Error> {
    let pool = web::Data::new(pg_pool); 
    let arc_config = web::Data::new(Arc::new(config)); // <---

    let server = HttpServer::new(move || {
        App::new()
            .service(exhaust)
            .app_data(pool.clone())
            .app_data(arc_config.clone()) // <---
    })
    .bind(addr)?
    .run();

Затем у меня есть обработчик, который пытается создать несколько потоков и передать эту структуру config в каждый:

#[get("/exhaust")]
pub async fn exhaust(pool: web::Data<PgPool>, config: web::Data<Arc<Settings>>) -> impl Responder {
    for _ in 1..16 {
        let handle = thread::spawn(move || {
            let inner_config = Arc::clone(&config);
            get_single_tweet(inner_config.as_ref().deref(), "1401287393228038149");
        });
    }

    HttpResponse::Ok()
}

Я думал, что, поскольку config уже обернут в Arc(), я должен иметь возможность просто Arc::clone() внутри каждого потока, а затем удалить ссылку в базовой переменной.

Но я получаю эту ошибку:

error[E0382]: use of moved value: `config`
  --> src/twitter/routes/pull.rs:63:36
   |
58 | pub async fn exhaust(pool: web::Data<PgPool>, config: web::Data<Arc<Settings>>) -> impl Responder {
   |                                               ------ move occurs because `config` has type `actix_web::web::Data<Arc<Settings>>`, which does not implement the `Copy` trait
...
63 |         let handle = thread::spawn(move || {
   |                                    ^^^^^^^ value moved into closure here, in previous iteration of loop
64 |             let inner_config = Arc::clone(&config);
   |                                            ------ use occurs due to use in closure

Я изо всех сил пытаюсь понять, почему это не удается. Если конфиг находится внутри Arc, то почему компилятор думает, что я пытаюсь его переместить вместо увеличения счетчика ссылок?

Я также пробовал ряд других подходов, все безуспешно:

  • Удаление move перед закрытием - компилятор жалуется, что заимствованное значение не живет достаточно долго
  • Разыменование config и перенос его в новый Arc() - ошибка аналогична исходной

Как правильно это сделать?


person ilmoi    schedule 06.06.2021    source источник


Ответы (1)


Вам нужно клонировать его до его перемещения. В противном случае ваша первая итерация обязательно должна будет принять его (поскольку нет гарантии, что config все еще будет существовать для клонирования при выполнении задачи). Затем вы получаете ошибку, которую вы видите для второй итерации, поскольку она тоже обязательно должна двигаться config; вот что имеется в виду под значением, перемещенным в закрытие здесь, в предыдущей итерации цикла.

#[get("/exhaust")]
pub async fn exhaust(pool: web::Data<PgPool>, config: web::Data<Arc<Settings>>) -> impl Responder {
    for _ in 1..16 {
        let handle = thread::spawn({
            // Clone this here, so the closure gets its very own to move-capture.
            let inner_config = config.get_ref().clone();
            move || {
                get_single_tweet(inner_config.as_ref().deref(), "1401287393228038149");
            });
        }
    }

    HttpResponse::Ok()
}

Обратите внимание, что сам web::Data уже является просто оболочкой вокруг Arc, так что у вас есть некоторая избыточность с вложенным Arc. Вы можете просто захотеть:

#[get("/exhaust")]
pub async fn exhaust(pool: web::Data<PgPool>, config: web::Data<Settings>) -> impl Responder {
    for _ in 1..16 {
        let handle = thread::spawn({
            let inner_config = config.clone();
            move || {
                get_single_tweet(inner_config.as_ref().deref(), "1401287393228038149");
            });
        }
    }

    HttpResponse::Ok()
}
person GManNickG    schedule 06.06.2021
comment
вау, так очевидно задним числом... большое спасибо. - person ilmoi; 07.06.2021