139

I have a class which holds a dictionary

class OrderBook:
    orders = {'Restaurant1': None,
              'Restaurant2': None,
              'Restaurant3': None,
              'Restaurant4': None}

    @staticmethod
    def addOrder(restaurant_name, orders):
        OrderBook.orders[restaurant_name] = orders

And I am running 4 threads (one for each restaurant) that call the method OrderBook.addOrder. Here is the function ran by each thread:

def addOrders(restaurant_name):

    #creates orders
    ...

    OrderBook.addOrder(restaurant_name, orders)

Is this safe, or do I have to use a lock before calling addOrder?

Wilfred Hughes
  • 29,846
  • 15
  • 139
  • 192
nmat
  • 7,430
  • 6
  • 30
  • 43

5 Answers5

126

Python's built-in structures are thread-safe for single operations, but it can sometimes be hard to see where a statement really becomes multiple operations.

Your code should be safe. Keep in mind: a lock here will add almost no overhead, and will give you peace of mind.

https://web.archive.org/web/20201108091210/http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm has more details.

Guy
  • 1,303
  • 1
  • 14
  • 20
Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • 1
    Should consider single operation vs. composite operations,such as **get-add-set**. – andy Dec 19 '14 at 01:57
  • 6
    problem is, when I'm frequently reading/writing that dict, that peace of mind will cost me a lot. – Shihab Shahriar Khan Feb 18 '17 at 19:44
  • 5
    "a lock here will add almost no overhead": why is that? – max Dec 14 '19 at 00:03
  • 2
    The link is dead, see it e.g. here: https://titanwolf.org/Network/Articles/Article?AID=41de1eb2-63d6-48f3-90a7-914940fbae81#gsc.tab=0 – Ruli Jan 24 '21 at 19:06
  • What about many add operations to something like a list. When a list resizes automatically, would the change of the reference to underlying containers cause any problems? – Zack Light Jun 28 '22 at 23:19
41

Yes, built-in types are inherently thread-safe: http://docs.python.org/glossary.html#term-global-interpreter-lock

This simplifies the CPython implementation by making the object model (including critical built-in types such as dict) implicitly safe against concurrent access.

  • 47
    This is not a feature of Python, but of *cpython*. – phihag Aug 05 '11 at 11:48
  • 9
    True, but as I understand it, built-ins in Jython and IronPython are also thread-safe even without the use of the GIL (and unladen swallow, should it ever emerge, proposes to do away with the GIL as well). I assumed that since he didn't specify the interpreter he was using, that he meant in CPython. –  Aug 05 '11 at 12:00
  • 3
    Correct in the case of Jython: http://www.jython.org/jythonbook/en/1.0/Concurrency.html#java-or-python-apis – Evgeni Sergeev Mar 06 '16 at 08:18
30

Google's style guide advises against relying on dict atomicity

This is explained in further detail at: Is Python variable assignment atomic?

Do not rely on the atomicity of built-in types.

While Python’s built-in data types such as dictionaries appear to have atomic operations, there are corner cases where they aren’t atomic (e.g. if __hash__ or __eq__ are implemented as Python methods) and their atomicity should not be relied upon. Neither should you rely on atomic variable assignment (since this in turn depends on dictionaries).

Use the Queue module's Queue data type as the preferred way to communicate data between threads. Otherwise, use the threading module and its locking primitives. Learn about the proper use of condition variables so you can use threading.Condition instead of using lower-level locks.


And I agree with this one: there is already the GIL in CPython, so the performance hit of using a Lock will be negligible. Much more costly will be the hours spent bug hunting in a complex codebase when those CPython implementation details change one day.

Federico Baù
  • 6,013
  • 5
  • 30
  • 38
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • Is there a rough estimate of what the lock overhead is. It's not like these cpython details are changing every day (if ever). – MrR Jun 22 '22 at 19:11
2

When using python’s builtin dict, set and get are atomic (because of cpython’s GIL).However, it seems like bad practice though since operations such as .items are not atomic.

Note - get-add-set operations are not thread-safe if multiple threads are working on the same dict keys.

ravi malhotra
  • 703
  • 5
  • 14
-3

dict() is not thread-safe. You just need to execute the following code to see a runtime error.

import threading

dic = dict()

def producer():
    for i in range(1000000):
        dic[i] = i

def consumer():
    for key, value in dic.items():
        print(key, value)

th1 = threading.Thread(target=producer)
th2 = threading.Thread(target=consumer)
th1.start()
th2.start()
th1.join()
th2.join()

RuntimeError: dictionary changed size during iteration

H.H
  • 305
  • 2
  • 9
  • 1
    This has nothing to do with thread safety. You can invalidate `dict`'s iterator and get the same error, using a single thread: `for key in (d := {1: 2, 3: 4}): del d[key]` – Expurple Dec 01 '22 at 21:49