Является ли черта Sync строгим подмножеством черты Send; что реализует синхронизацию без отправки?

avatar
Evan Carroll
8 августа 2021 в 20:58
780
3
4

В "Programming Rust, 2nd Edition" Джима Бланди, Джейсона Орендорфа, Леоноры Ф.С. Тиндалл на стр. 520 есть график, на котором показаны функции «Отправить» и «Синхронизация» с перекрывающимися кругами, при этом «Синхронизация» полностью включена в «Отправить».

Figure 19-9. Send and Sync types

Это наводит меня на мысль, что все, что реализует Sync, должно также реализовывать Send, но этот пример со страницы 561 и все, что я видел, всегда указывает их обоих по отдельности,

type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>

Почему, если 100% элементов, реализующих синхронизацию, также являются отправкой, разве синхронизация не является подчеркнутой отправкой? Почему границы признаков должны указывать оба? Почему люди отмечают оба. Есть ли какой-либо вариант использования чего-то, что должно быть синхронизировано, а не отправлено? В каком случае вы можете поделиться изменяемой ссылкой с другим потоком, но не передать права собственности этому другому потоку?

Источник
Matthieu M.
9 августа 2021 в 16:32
0

Я считаю, что это, в конечном счете, дубликат coderhelper.com/questions/28387421/…. Однако я подожду подтверждения, так как мои голоса за обман - золотые...

Ответы (3)

avatar
Alice Ryhl
9 августа 2021 в 07:51
6

Когда тип реализует черту Send, это позволяет выполнять следующие действия:

  1. Изменяемый доступ к значению из потоков, отличных от того, в котором оно было создано. Это включает в себя его удаление.
  2. Неизменный доступ к значению из потоков, отличных от того, из которого оно было создано. (Но не обязательно параллельно.)

Когда тип реализует черту Sync, это позволяет выполнять следующие действия:

  1. Неизменный доступ к значению из нескольких параллельных потоков.

Еще один способ «определить» значение T: Sync — просто выяснить, безопасно ли для &T быть Send. Это имеет смысл, поскольку &T можно копировать, поэтому создание копий и отправка их в разные потоки обеспечит параллельный неизменяемый доступ.

.

Если значение равно Send + !Sync, то к нему можно получить доступ любым способом из любого потока, но только из одного потока одновременно, даже если доступ неизменяем.

Если значение равно !Send + Sync, то к нему можно получить постоянный доступ из любого потока и из нескольких потоков параллельно. Однако изменяемый доступ должен осуществляться в потоке, в котором он был создан.


Некоторые примеры:

  • MutexGuard - уничтожение MutexGuard в другом потоке нецелесообразно, поэтому это не может быть Send. Однако, если к значению внутри можно получить неизменяемый доступ из нескольких параллельных потоков, тогда такой неизменяемый доступ также будет безопасным для самого MutexGuard.
  • .
  • SyncWrapper — неизменяемая ссылка на SyncWrapper<T> вообще не позволяет выполнять какие-либо действия, поэтому для нее всегда безопасно быть Sync. (Связанный ящик требует, чтобы внутреннее значение было Send, но это строже, чем необходимо)
  • &T — поскольку неизменяемые ссылки можно копировать, возможность отправки одной из них в другой поток позволит выполнять неизменяемый доступ из нескольких потоков параллельно. Таким образом, &T может быть Send, только если T равно Sync. Нет необходимости, чтобы T был Send, так как &T не допускает изменяемый доступ.
  • &mut T - изменяемые ссылки не могут быть скопированы, поэтому отправка их в другие потоки не разрешает доступ из нескольких потоков параллельно, поэтому &mut T может быть Send, даже если T не Sync. Конечно, T по-прежнему должно быть Send.
  • Arc<T> — в основном это ведет себя как &T. Его можно клонировать, поэтому для его отправки в другие потоки требуется T: Sync. Однако для этого также требуется T: Send, так как последний Arc<T> может быть отброшен в поток, отличный от того, в котором был создан T, что невозможно сделать без Send.<62985363>7.
  • RefCell<T> — этот тип никогда не может быть Sync, потому что вы можете изменить значение внутри только с помощью неизменяемой ссылки, и это будет гонка данных, если вы сможете делать это из нескольких потоков параллельно. Нет проблем с тем, что RefCell<T> является Send при условии, что T является
  • .
  • Rc<T> — если у вас есть два клона одного и того же Rc<T>, то это будет гонка данных для доступа к ним из разных потоков параллельно. Это исключает как Send, так и Sync, поскольку оба они разрешают неизменяемый доступ из других потоков, и этот другой поток может использовать это для удаленного вызова .clone() и получения Rc<T> в другом потоке.
Evan Carroll
3 сентября 2021 в 19:23
0

Как правило, мне нравится форма этого ответа, я бы хотел, чтобы вы включили / обратились к этому из другого ответа с одобрением Единственная связь между отправкой и синхронизацией заключается в том, что T является синхронизацией тогда и только тогда, когда &T является отправкой (это имеет смысл, поскольку " синхронизация" между потоками на самом деле просто возможность поделиться ссылкой на него между потоками). Я думаю, что это важно, но в целом я думаю, что это образцово. Например, если я помечаю &T как Send, синхронизируется ли T автоматически?

Alice Ryhl
3 сентября 2021 в 19:47
0

Я добавил еще несколько примеров, включая ссылочные типы по запросу. Как правило, компилятор не может сделать вывод, что T должно быть Sync, если &T равно Send (см. Отношения T: Send> <-> &T: Sync действительно двусторонние в том смысле, что нет противоположных примеров.

avatar
Colonel Thirty Two
8 августа 2021 в 21:20
2

Возможна структура, которая реализует Sync, но не Send:


struct Foo(*const ());
unsafe impl Sync for Foo {}

fn main() {
    // Note: static just for demo using standard library threads.
    // Can use borrowed thread libraries with this too, where the lifetime isn't static.
    let static_foo_ref = Box::leak(Box::new(Foo(std::ptr::null()))) as &'static Foo;
    
    std::thread::spawn(move || {
        println!("Borrow thread, foo ptr: {:?}", static_foo_ref.0);
    }).join().unwrap();
    
    // Doesn't work
    /*let foo_owned = Foo(std::ptr::null());
    std::thread::spawn(move || {
        println!("Owned thread, foo ptr: {:?}", foo_owned.0);
    }).join().unwrap();*/
}

avatar
Aplet123
8 августа 2021 в 21:19
8

Кажется, книга неправильная. Единственная связь между Send и Sync заключается в том, что T является Sync тогда и только тогда, когда &T является Send (это имеет смысл, поскольку «синхронизация» между потоками на самом деле просто возможность поделиться ссылкой к нему через потоки). На самом деле в стандартной библиотеке даже есть тип Sync, но не Send: MutexGuard. Причина в том, что базовая реализация приводит к неопределенному поведению при попытке разблокировать мьютекс из потока, отличного от потока, который его заблокировал.