Concorrência Extensível com Send e Sync
Curiosamente, quase todos os recursos de concorrência dos quais falamos até agora neste capítulo fazem parte da biblioteca padrão, não da linguagem. Suas opções para lidar com concorrência não estão limitadas à linguagem nem à biblioteca padrão; você pode escrever seus próprios recursos de concorrência ou usar aqueles escritos por outras pessoas.
No entanto, entre os principais conceitos de concorrência incorporados à linguagem,
em vez de à biblioteca padrão, estão as traits Send e Sync de std::marker.
Transferindo Ownership Entre Threads
A trait marcadora Send indica que o ownership de valores do tipo que
implementa Send pode ser transferido entre threads. Quase todos os tipos de Rust
implementam Send, mas há algumas exceções, incluindo Rc<T>: ele
não pode implementar Send porque, se você clonasse um valor Rc<T> e tentasse
transferir o ownership do clone para outra thread, ambas as threads poderiam atualizar
a contagem de referências ao mesmo tempo. Por essa razão, Rc<T> é implementado
para uso em situações single-threaded, em que você não quer pagar a penalidade
de desempenho da thread safety.
Portanto, o sistema de tipos de Rust e os trait bounds garantem que você nunca
possa enviar acidentalmente um valor Rc<T> entre threads de forma insegura.
Quando tentamos fazer isso na Listagem 16-14, obtivemos o erro de que a trait
Send não está implementada para Rc<Mutex<i32>>. Quando mudamos para Arc<T>, que
implementa Send, o código passou a compilar.
Qualquer tipo composto inteiramente por tipos Send também é automaticamente marcado como Send.
Quase todos os tipos primitivos são Send, exceto os ponteiros brutos, que
discutiremos no Capítulo 20.
Acessando a Partir de Múltiplas Threads
A trait marcadora Sync indica que é seguro que o tipo que implementa
Sync seja referenciado a partir de múltiplas threads. Em outras palavras,
qualquer tipo T implementa Sync se &T (uma referência imutável a T)
implementa Send, o que significa que a referência pode ser enviada com
segurança para outra thread. Assim como Send, todos os tipos primitivos
implementam Sync, e tipos compostos inteiramente por tipos que implementam
Sync também implementam Sync.
O smart pointer Rc<T> também não implementa Sync pelos mesmos motivos
pelos quais não implementa Send. O tipo RefCell<T> (do qual falamos
no Capítulo 15) e a família de tipos Cell<T> relacionada não implementam
Sync. A implementação de verificação de borrowing que RefCell<T> faz em
tempo de execução não é thread-safe. O smart pointer Mutex<T> implementa
Sync e pode ser usado para compartilhar acesso com múltiplas threads, como
você viu em “Acesso Compartilhado a Mutex<T>”.
Implementar Send e Sync Manualmente é Unsafe
Como os tipos compostos inteiramente por outros tipos que implementam as traits Send e
Sync também implementam automaticamente Send e Sync, não precisamos
implementar essas traits manualmente. Como traits marcadoras, elas nem sequer possuem
métodos a implementar. Elas são úteis apenas para impor invariantes relacionados à
concorrência.
A implementação manual dessas traits envolve implementar código Rust unsafe.
Falaremos sobre o uso de código Rust unsafe no Capítulo 20; por enquanto, a
informação importante é que construir novos tipos concorrentes que não sejam
compostos por partes Send e Sync requer reflexão cuidadosa para preservar
as garantias de segurança. “O Rustonomicon” tem mais informações
sobre essas garantias e sobre como preservá-las.
Resumo
Esta não é a última vez que você verá concorrência neste livro: o próximo capítulo se concentra em programação async, e o projeto do Capítulo 21 usará os conceitos deste capítulo em uma situação mais realista do que os exemplos menores discutidos aqui.
Como mencionado anteriormente, como muito pouco da forma como Rust lida com concorrência faz parte da linguagem, muitas soluções de concorrência são implementadas como crates. Elas evoluem mais rapidamente do que a biblioteca padrão, portanto pesquise online pelos crates atuais e de ponta para usar em situações multithreaded.
A biblioteca padrão do Rust fornece canais para passagem de mensagens e tipos de
smart pointer, como Mutex<T> e Arc<T>, que são seguros para uso em
contextos concorrentes. O sistema de tipos e o borrow checker garantem que o
código que usa essas soluções não resultará em corridas de dados nem em referências inválidas.
Depois de compilar seu código, você pode ter certeza de que ele rodará
tranquilamente em múltiplas threads sem os tipos de bugs difíceis de rastrear
comuns em outras linguagens. Programação concorrente não é mais um conceito a temer:
vá em frente e faça seus programas concorrentes, sem medo!