Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tackling Thread Safety in Python

Tackling Thread Safety in Python

Adarsh D

July 14, 2024
Tweet

More Decks by Adarsh D

Other Decks in Programming

Transcript

  1. Outline • Threading • Race Conditions • Thread Safety •

    Synchronization primitives • Making Programs thread safe
  2. Threading Why use threading? • To improve application efficiency (concurrent

    execution, improve responsiveness) Sample - A banking app
  3. Banking App - Initial Balance id name balance 1 John

    100 2 Jane 100 3 Alice 100 Money in bank - 300
  4. id name balance 1 John 90 2 Jane 100 3

    Alice 110 Money in bank - 300 Banking App - Balance After Transfer
  5. Banking App - Balance After Transfer id name balance 1

    John 90 2 Jane 90 3 Alice 110 Money in bank - 290
  6. Banking App - Debugging the Issue Concurrent read & write

    happens due to threading This can lead to race conditions
  7. Race Conditions Race conditions occur when we work with shared

    mutable data Non atomic operations can get context switched in between
  8. Race Conditions Consider two threads in our banking app. A

    user has an initial balance of 30. An amount of 100 is being transferred simultaneously to the user by each of the thread. 1. Thread 1 reads the current balance (30) 2. Thread 1 updates the current balance (130) 3. Before thread 1 saves to the database, thread 2 reads the value of A (gets 30) 4. Thread 2 updates balance as 130. 5. Thread 2 writes value of 130 to DB; thread 1 also does the same. The problem is that, a read is allowed midway of another modify operation.
  9. Thread Safety A program is said to be thread-safe if

    it can be run using multiple threads without any unexpected side effects (like the one we seen in banking example)
  10. When should we worry about Thread safety Is using threading

    as our concurrency framework Ref: Anthony Shaw - Unlocking the Parallel Universe: Subinterpreters and Free-Threading in Python 3.13 - Pycon US 2024
  11. When should we worry about Thread safety Has shared mutable

    data & has non-atomic operations - Threads share memory location of parent process - No problem if no data is shared - No problem if code executed with threads is immutable and atomic
  12. Non thread safe examples - Print • Print function operation

    - prints the value, Then prints separator and end (by default \n) • It is thread unsafe because the operation is non atomic • Context switch can happen in between
  13. Making programs thread safe • Don’t use threads (go with

    other concurrency frameworks) • Make operations atomic • Don’t share mutable data across threads • Use Synchronization primitives.
  14. Synchronization Primitives - Lock A Lock is a synchronization primitive

    that allows only one thread to access a resource at a time. Practical Use-Case: Ensuring that only one thread can modify a shared variable at a time to prevent race conditions.
  15. Synchronization Primitives - RLock An RLock is a reentrant lock

    that allows the same thread to acquire the lock multiple times without causing a deadlock. Practical Use-Case: Allowing a thread to re-enter a critical section of code that it already holds the lock for, such as in recursive functions.
  16. Synchronization Primitives - Semaphore A Semaphore is a synchronization primitive

    that controls access to a resource by maintaining a counter, allowing a set number of threads to access the resource simultaneously. Practical Use-Case: Limiting the number of concurrent connections to a database to prevent overload. (eg: connection pooling)
  17. Synchronisation Primitives - Event An Event is a synchronization primitive

    that allows one thread to signal one or more other threads that a particular condition has been met. Practical Use-Case: Notifying worker threads that new data is available for processing.
  18. Synchronization Primitives - Condition A Condition is a synchronization primitive

    that allows threads to wait for certain conditions to be met before continuing execution. Practical Use-Case: Pausing a thread until a specific condition is met, such as waiting for a queue to be non-empty before consuming an item.
  19. Synchronization Primitives - Barrier A Barrier is a synchronization primitive

    that allows multiple threads to wait until all threads have reached a certain point before any of them can proceed. Practical Use-Case: Ensuring that all worker threads complete their individual tasks before any thread proceeds to the next phase of a multi-phase computation.
  20. Making Programs Thread Safe - Banking App - The example

    here (using Python locks) is not suitable for production. - Multiple instances of our Python app can be deployed across regions. - Enable locks at the source of truth level. - Enable locks at the database level
  21. Summary - Before moving to multithreading keep in mind that

    the code you are working with might not be designed for thread safety - even library code. - Before switching to multithreading, check for shared mutable data & atomicity requirements. - Add synchronization primitives to enforce thread-safety. “When in doubt, use a mutex!” - CPython docs ( https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe )