16. Fearless Concurrency

Learning objectives

  • How to create threads to run multiple pieces of code at the same time
  • Message-passing concurrency: where channels send messages between threads
  • Shared-state concurrency: where multiple threads share access to some piece of data
  • Rust’s Sync and Send traits

Introduction

Concurrent vs. Parallel

  • Concurrent programming -> different parts of a program execute independently
  • Parallel programming -> different parts of a program execute at the same time
  • “Fearless Concurrency” applies to both concurrent and parallel programming

Fearless Concurrency with Rust

  • Rust’s ownership and type system manages memory safety AND concurrency problems
  • This helps convert run-time errors into compile-time errors
  • You can fix your code while you are working on it, rather than after it has been shipped

Opearting systems run programs concurrently

  • Operating systems run executed code in a process
  • Multiple processes can be run at once

Programs can run their parts concurrently as well

  • Within a program, independent parts can also run simultaneously
  • The features that run these independent parts are called threads
  • e.g. Web Servers have multiple threads to respond to multiple requests at once

Typical challenges of concurrency

  • Race conditions: threads accessing resources in an unexpected order
  • Deadlocks: where two threads are waiting for each other, preventing progress
  • Bugs that only happen in certain contexts and are difficult to reproduce

Thread models

  • Rust’s standard library uses a 1:1 model of thread implementation
    • A program uses one operating system thread per one language thread
  • Different crates offer different implementations, with different trade-offs

Creating Threads

Spawning a thread in Rust

  • To create a new thread, we use thread::spawn, and pass it to a closure
  • The closure should contain the code that we want to run in that thread
  • When the main thread of a Rust program completes, all spawned threads are shut down
  • Code example!

Join handles

  • Calling join on the handle blocks the currently running thread
  • It unblocks when the thread represented by the handle terminates
  • Code examples!

Transfer ownership with move

  • Using the move keyword with closures passed to thread::spawn
  • This transfer ownership of the values it uses from the environment to the thread
  • Code example!

Message Passing Concurrency

  • Threads communicate to each other by passing messages with data
  • “Do not communicate by sharing memory, share memory by communicating”

Channels

  • In Rust, message passing concurrency is achieved using channels
  • Channels can be thought of as the directional channel of a river
  • Messages placed in the channel travel downstream until the end

Transmitters and Receivers

  • Upstream: code calls methods on the transmitter with the data you want to send
  • Downstream: code checks the receiving end for arriving messages
  • A transmitter and receiver can be initialized with mpsc::channel()
  • Code example!

Shared-State Concurrency

  • Instead of passing messages, multiple threads could also access the same shared data
  • Channels are similar to single ownership
  • Shared memory is similar to shared ownership
  • Mutexes are a common primitive used for sharing memory between owners

Mutexes

  • Short for Mutual exclusion
  • A mutex allows only one owner to access data at any given time
  • To access data in a mutex, a thread must ask to acquire the mutex’s lock
  • The lock keeps track of who currently has exclusive access to the data
  • “Mutexes guard the data they hold with a locking system”

Two rules of mutexes

  • Attempt to acquire their lock before using their data
  • When you’re done with the data, you must unlock the data
  • Code example!

Sharing a Mutex between threads

  • Mutex<T> is not safe to share between multiple threads by default
  • Use Arc (Atomic Reference Counting) to enable multiple ownership.
  • Code example!

Extending Concurrency with Sync and Send

  • Concurrency can be extended in Rust using the Sync and Send traits
  • Send indicates that ownership of values of that type can move between threads
  • Sync indicates that it is safe for that type to be referenced from multiple threads