Thread Safety Guarantees¶
This page documents thread-safety guarantees for built-in types in Python’sfree-threaded build. The guarantees described here apply when using Python withtheGIL disabled (free-threaded mode). When the GIL is enabled, mostoperations are implicitly serialized.
For general guidance on writing thread-safe code in free-threaded Python, seePython support for free threading.
Thread safety for list objects¶
Reading a single element from alist isatomic:
lst[i]# list.__getitem__
The following methods traverse the list and useatomicreads of each item to perform their function. That means that they mayreturn results affected by concurrent modifications:
iteminlstlst.index(item)lst.count(item)
All of the above operations avoid acquiringper-object locks. They do not block concurrent modifications. Otheroperations that hold a lock will not block these from observing intermediatestates.
All other operations from here on block using theper-object lock.
Writing a single item vialst[i]=x is safe to call from multiplethreads and will not corrupt the list.
The following operations return new objects and appearatomic to other threads:
lst1+lst2# concatenates two lists into a new listx*lst# repeats lst x times into a new listlst.copy()# returns a shallow copy of the list
The following methods that only operate on a single element with no shiftingrequired areatomic:
lst.append(x)# append to the end of the list, no shifting requiredlst.pop()# pop element from the end of the list, no shifting required
Theclear() method is alsoatomic.Other threads cannot observe elements being removed.
Thesort() method is notatomic.Other threads cannot observe intermediate states during sorting, but thelist appears empty for the duration of the sort.
The following operations may allowlock-free operations to observeintermediate states since they modify multiple elements in place:
lst.insert(idx,item)# shifts elementslst.pop(idx)# idx not at the end of the list, shifts elementslst*=x# copies elements in place
Theremove() method may allow concurrent modifications sinceelement comparison may execute arbitrary Python code (via__eq__()).
extend() is safe to call from multiple threads. However, itsguarantees depend on the iterable passed to it. If it is alist, atuple, aset, afrozenset, adict or adictionary view object (but not their subclasses), theextend operation is safe from concurrent modifications to the iterable.Otherwise, an iterator is created which can be concurrently modified byanother thread. The same applies to inplace concatenation of a list withother iterables when usinglst+=iterable.
Similarly, assigning to a list slice withlst[i:j]=iterable is safeto call from multiple threads, butiterable is only locked when it isalso alist (but not its subclasses).
Operations that involve multiple accesses, as well as iteration, are neveratomic. For example:
# NOT atomic: read-modify-writelst[i]=lst[i]+1# NOT atomic: check-then-actiflst:item=lst.pop()# NOT thread-safe: iteration while modifyingforiteminlst:process(item)# another thread may modify lst
Consider external synchronization when sharinglist instancesacross threads.
Thread safety for dict objects¶
Creating a dictionary with thedict constructor is atomic when theargument to it is adict or atuple. When using thedict.fromkeys() method, dictionary creation is atomic when theargument is adict,tuple,set orfrozenset.
The following operations and functions arelock-free andatomic.
d[key]# dict.__getitem__d.get(key)# dict.getkeyind# dict.__contains__len(d)# dict.__len__
All other operations from here on hold theper-object lock.
Writing or removing a single item is safe to call from multiple threadsand will not corrupt the dictionary:
d[key]=value# writedeld[key]# deleted.pop(key)# remove and returnd.popitem()# remove and return last itemd.setdefault(key,v)# insert if missing
These operations may compare keys using__eq__(), which canexecute arbitrary Python code. During such comparisons, the dictionary maybe modified by another thread. For built-in types likestr,int, andfloat, that implement__eq__() in C,the underlying lock is not released during comparisons and this is not aconcern.
The following operations return new objects and hold theper-object lockfor the duration of the operation:
d.copy()# returns a shallow copy of the dictionaryd|other# merges two dicts into a new dictd.keys()# returns a new dict_keys view objectd.values()# returns a new dict_values view objectd.items()# returns a new dict_items view object
Theclear() method holds the lock for its duration. Otherthreads cannot observe elements being removed.
The following operations lock both dictionaries. Forupdate()and|=, this applies only when the other operand is adictthat uses the standard dict iterator (but not subclasses that overrideiteration). For equality comparison, this applies todict andits subclasses:
d.update(other_dict)# both locked when other_dict is a dictd|=other_dict# both locked when other_dict is a dictd==other_dict# both locked for dict and subclasses
All comparison operations also compare values using__eq__(),so for non-built-in types the lock may be released during comparison.
fromkeys() locks both the new dictionary and the iterablewhen the iterable is exactly adict,set, orfrozenset (not subclasses):
dict.fromkeys(a_dict)# locks bothdict.fromkeys(a_set)# locks bothdict.fromkeys(a_frozenset)# locks both
When updating from a non-dict iterable, only the target dictionary islocked. The iterable may be concurrently modified by another thread:
d.update(iterable)# iterable is not a dict: only d lockedd|=iterable# iterable is not a dict: only d lockeddict.fromkeys(iterable)# iterable is not a dict/set/frozenset: only result locked
Operations that involve multiple accesses, as well as iteration, are neveratomic:
# NOT atomic: read-modify-writed[key]=d[key]+1# NOT atomic: check-then-act (TOCTOU)ifkeyind:deld[key]# NOT thread-safe: iteration while modifyingforkey,valueind.items():process(key)# another thread may modify d
To avoid time-of-check to time-of-use (TOCTOU) issues, use atomicoperations or handle exceptions:
# Use pop() with default instead of check-then-deleted.pop(key,None)# Or handle the exceptiontry:deld[key]exceptKeyError:pass
To safely iterate over a dictionary that may be modified by anotherthread, iterate over a copy:
# Make a copy to iterate safelyforkey,valueind.copy().items():process(key)
Consider external synchronization when sharingdict instancesacross threads.