Lock-free Programming

  • Threads execute critical sections concurrently

    • Without data races
    • But without using the operating system's locking facilities
  • Avoids or reduces some of the drawbacks to using locks

    • Race conditions caused by forgetting to lock, or using the wrong mutex
    • Lack of composability
    • Risk of deadlock
    • High overhead
    • Lack of scalability caused by coarse-grained locking
    • Code complexity and increased overhead caused by fine-grained locking

Locking vs Lock-free

  • Both Programming styles are used to manage shared state

    • Analogous to managing a traffic intersection
  • Locks

    • Traffic lights control access
    • Stop and wait until able to proceed into critical section
  • Lock-free

    • Motorway style intersection
    • Traffic from different levels can go over the same section at the same time
    • Traffic from one level can merge with traffic from a different level without stopping
    • If not done carefully, collisions can occur!

Advantages of Lock-free Programming

  • If done correctly, threads can never block each other
    • No possibility of deadlock or livelock
    • If a thread is blocked, other threads can continue to execute
    • Useful if work must be completed within a time limit (e.g. real time systems)

Drawbacks of Lock-free Programming

  • Very difficult to write code which is correct and efficient

  • The extra complexity makes it unsuitable for many applications

    • e.g. user interface code with separation of concerns
    • May be useful in performance critical code, such as infrastructure
  • Should be used only if

    • A data structure in the program is subject to high contention
    • Which causes unacceptable performance
    • And the lock free version brings performance up to acceptable levels

The Everyday World of Programming with Locks

  • We can make some very useful assumptions

  • Global state is consistent

    • Provided we only access shared data inside a locked region
    • No other threads will see our changes
    • Until the lock is released
  • Logical consistency

    • When working inside a locked region, global state will not change
    • e.g. between evaluating an "if" statement and executing the body
  • Code order

    • Statements will execute in the same order as in the source code
    • Or at least, they will appear to...

The Strange World of Lock-free Programming

  • None of these assumptions apply to lock-free programs
    • Shared data may have different values in different threads
    • The value may change between an "if" statement and its body
    • Statements may execute in a different order from the source code

Transactions

  • Transactional model of lock-free programming

    • "ACID"
  • Atomic / All or Nothing

    • A transaction either completes successfully ("commit")
    • Or it fails and leaves everything as it was ("rollback")
  • Consistent

    • The transaction takes the database from one consistent state to another
    • As seen by other users, the database is never in an inconsistent state
  • Isolated

    • Two transactions can never work on the same data simultaneously
  • Durable

    • Once a transaction is committed, it cannot be overwritten
    • ... Until the next transaction sees the result of the commit
    • There is no possibility of "losing" an update

Transactional Memory

  • Put shared data in transactional memory

  • All operations on shared data will be transactional

  • However, there is no standard implementation in C++

  • The only way to write a lock-free program is to use atomic instructions

  • We need to think very carefully about thread interactions

    • Other threads can interleave between each statement
    • Or between expressions within statements
    • How do concurrent writers interact with each other?
    • How to concurrent writers interact with concurrent readers?