Как я могу вернуть тип, содержащий Rc из потока? - PullRequest
0 голосов
/ 30 января 2019

Я порождаю поток, который строит то, что по сути является деревом, которое понадобится через некоторое время и, следовательно, может быть вычислено вне потока.Для примера, подумайте о Foo { inner: HashMap<Bar, Rc<FooBar>> }.Закрытие потока не захватывает ничего из окружающей среды.

Проблема в том, что Rc делает весь тип !Send.Это исключает возможность возврата Foo в нерестовую нить.Фактически, Rc "отравляет" Foo.

Я не вижу причины, по которой тип, который создается порожденным потоком и возвращается, все еще должен быть Send: он вызывает все внутренние типы(в этом случае Rc в HashMap, в Foo, должно быть Send. Это невозможно исправить с точки зрения порожденной нити. Это заставляет порожденную нить все время использовать атомный счетв то время как фактически у обоих потоков отсутствует возможность одновременного доступа к refcounter.

Есть ли способ вернуть (не поделиться!) тип, содержащий Rc из потока? Я что-то упустил впонимание Send?

Ответы [ 2 ]

0 голосов
/ 30 января 2019

Rust язык ничего не знает о том факте, что когда поток заканчивается, он больше не может иметь никаких ссылок.На самом деле, он ничего не знает о потоках «начало» или «окончание».

С языковой точки зрения, возможно, что один из клонов Rc все еще принадлежитнить.Когда тип находится в потоке, это конец.


Если у вас есть что-то, что вы знаете, программист, но компилятор не может, это случай для unsafe кода.

Я не уверен на 100%, но, насколько мне известно, , если все связанные Rc находятся в одном потоке, это действительно безопасно.Этот должен использоваться для возврата значения из потока.

use std::{rc::Rc, thread};

type MyTreeThing = Rc<i32>;
struct ReturningAnRcAcrossThreadsIsSafe(MyTreeThing);

// I copied this from Stack Overflow without reading the text and
// stating why I think this code is actually safe.
unsafe impl Send for ReturningAnRcAcrossThreadsIsSafe {}

fn main() {
    let t = thread::spawn(|| ReturningAnRcAcrossThreadsIsSafe(Rc::new(42)));

    let a = t.join().unwrap().0;

    println!("{}", a);
}

Этот ответ отличается от ответа trentcl тем, что небезопасный код имеет гораздо меньшую область действия- им покрывается только значение, которое возвращается через поток.Их ответ помечает весь тип MyTreeThing как безопасный для отправки между потоками, независимо от контекста.Это нормально, если вы позаботились о том, чтобы все связанные Rc всегда перемещались оптом между нитями.

См. Также:

0 голосов
/ 30 января 2019

[T] здесь фактически нулевая возможность для обоих потоков одновременно обращаться к refcounter.

Но компилятор не может этого понять, так что у вас естьсказать "нет на самом деле, поверь мне, я знаю, это выглядит глупо, но это на самом деле безопасно".Вот для чего unsafe.

Это должно быть так просто, как

unsafe impl Send for Foo {}

От Nomicon :

  • Rc не является отправкой или синхронизацией (потому что refcount является общим и несинхронизированным).

Не безопасно делать Rc сам Send, потому что Rc выставляетинтерфейс, который был бы небезопасен, если бы пересылался между потокамиНо Foo, как вы его описываете, предоставляет интерфейс, который не небезопасен для отправки между потоками, так что просто unsafe impl это и будет на вашем пути.

Предполагая, чтоявляется то, что интерфейс является безопасным для отправки между потоками.Foo не может иметь метод, который клонирует и возвращает внутренний Rc, например (потому что вы могли бы использовать его, чтобы эффективно "переправить" голое Rc в другой поток через Foo).

Если Foo не всегда безопасно для отправки, но вы знаете, что некоторые определенные Foo безопасны для отправки, лучше было бы временно обернуть ихв типе, который Send, как ответ Шепмастера предлагает.

...