Ответ 1
Cell
и RefCell
реализовать Send
но не Sync
, потому что они могут быть безопасно отправлены между потоками, но не разделены между ними.
Чтобы лучше понять признаки Send
и Sync
, есть примеры типов, которые:
Send
и не выполнять Sync
.Sync
и не реализуйте Send
. Cell
и RefCell
реализовать Send
но не Sync
, потому что они могут быть безопасно отправлены между потоками, но не разделены между ними.
Прежде всего, важно понять, что большинство структур (или перечислений) Send
:
Send + 'static
'a
, может быть Send + 'a
В результате вы обычно ожидаете, что любой Sync
struct
будет Send
тоже, потому что Send
- это такая простая панель для достижения (по сравнению с гораздо более сложной строкой Sync
, которая требует безопасная одновременная модификация из нескольких потоков).
Однако ничто не мешает создателю определенного типа отмечать его как не Send
. Например, позвольте реанимировать условия!
Идея условий в Lisp заключается в том, что вы настраиваете обработчик для данного условия (скажем: FileNotFound
), а затем, когда в глубине стека выполняется это условие, вызывается ваш обработчик.
Как вы реализуете это в Rust?
Хорошо, чтобы сохранить независимость потоков, вы должны использовать локальное хранилище потоков для обработчиков условий (см. std::thread_local!
). Каждое условие было бы стеком обработчиков условий, причем либо только верхний вызываемый, либо итеративный процесс, начиная с верхнего, но до тех пор, пока не будет успешным.
Но тогда, как бы вы их установили?
Лично я бы использовал RAII! Я бы привязал обработчик условия в локальном стеке потока и зарегистрировал его в кадре (например, используя навязчивый дважды связанный список как стек).
Таким образом, когда я закончил, обработчик условия автоматически самостоятельно регистрирует себя.
Конечно, система должна учитывать, что пользователи делают неожиданные вещи (например, хранение обработчиков условий в куче и не отбрасывание их в том порядке, в котором они были созданы), и именно поэтому мы используем двусвязный список, поэтому что при необходимости обработчик может самостоятельно зарегистрировать себя из середины стека.
Итак, мы имеем:
struct ConditionHandler<T> {
handler: T,
prev: Option<*mut ConditionHandler<T>>,
next: Option<*mut ConditionHandler<T>>,
}
а "реальный" обработчик передается пользователем как T
.
Будет ли этот обработчик Sync
?
Возможно, зависит от того, как вы его создаете, но нет причин, по которым вы не могли бы создать обработчик, чтобы ссылка на него не могла быть разделена между несколькими потоками.
Примечание. Эти потоки не могли получить доступ к своим элементам данных prev
/next
, которые являются частными и не должны быть Sync
.
Будет ли этот обработчик Send
?
Если не будет предпринята особая осторожность, нет.
Поля prev
и next
не защищены от параллельных доступов и еще хуже, если обработчик должен быть удален, а другой поток получил ссылку на него (например, другой обработчик, пытающийся самостоятельно зарегистрировать себя), то эта теперь свисающая ссылка вызовет Undefined Поведение.
Примечание: последний выпуск означает, что просто переключение Option<*mut Handler<T>>
для AtomicPtr<ConditionHandler<T>>
недостаточно; см. Общие ошибки в написании алгоритмов без блокировки для более подробной информации.
И там у вас есть: a ConditionHandler<T>
есть Sync
, если T
есть Sync
, но никогда не будет Send
(как есть).
Для полноты многие типы реализуют Send
, но не Sync
(большинство типов Send
, на самом деле): Option
или Vec
например.