Working with Shared Data

Will be covering

  • Basic mutex class (std::mutex)
  • Mutex wrappers (std::lock_guard and std::unique_lock)
  • Mutexes and time-outs (std::timed_mutex, std::unique_lock)
  • Shared mutexes (std::shared_mutex and std::shared_lock)
  • Static and thread-local data
  • Deadlock
  • Deadlock avoidance (std::scoped_lock, std::lock())
  • Livelock and Livelock avoidance

Locking Guidelines

  • Locking impacts on other threads

    • They will have to wait longer for a resource they need
    • This affects performance
  • Always hold a lock for the shortest possible time

  • Avoid locking lengthy operations if possible

    • e.g input/output

Recommendations for Reading Shared Data

  • Reading
    • Lock
    • Make a copy of the shared data
    • Unlock and process the copy

Recommendations for Writing Shared Data

  • Writing
    • Lock
    • Make a copy of the shared data
    • Unlock and process the copy
    • Lock again
    • Update the shared data from the copy
    • Unlock

Locking Guidelines for Data Structures

  • Do not lock any more elements than necessary

    • e.g. Accessing a single element in a linked list
    • Do not lock the entire list
    • This will block other threads from accessing unrelated elements
  • Do not make locking too fine-grained

    • Do not lock individual elements when inserting and deleting
    • Another thread may need to access a neighbouring element
    • Data race
    • Need to lock neighbouring elements in a single operation

Pros and Cons of Mutexes

  • Fairly straightforward way to protect shared data

    • Prevent data races and race conditions
  • Locking and unlocking are slow operations

  • Low level

    • The programmer must remember to use a mutex
    • The programmer must use the right mutex
    • The programmer must understand how different threads can modify the data
  • Most real world programs use higher level structures

    • Mutex wrapper class
    • Classes from the next section