I notice that it is often suggested to use queues with multiple threads, instead of lists and .pop()
. Is this because lists are not thread-safe, or for some other reason?

- 75,622
- 18
- 128
- 150

- 4,205
- 4
- 31
- 38
-
4Hard to tell always what exactly is guaranteed thread-safe in Python, and it's hard to reason about thread safety in it. Even the highly popular Bitcoin wallet Electrum has had concurrency bugs likely stemming from this. – sudo Jul 20 '18 at 02:06
4 Answers
Lists themselves are thread-safe. In CPython the GIL protects against concurrent accesses to them, and other implementations take care to use a fine-grained lock or a synchronized datatype for their list implementations. However, while lists themselves can't go corrupt by attempts to concurrently access, the lists's data is not protected. For example:
L[0] += 1
is not guaranteed to actually increase L[0] by one if another thread does the same thing, because +=
is not an atomic operation. (Very, very few operations in Python are actually atomic, because most of them can cause arbitrary Python code to be called.) You should use Queues because if you just use an unprotected list, you may get or delete the wrong item because of race conditions.

- 130,178
- 23
- 148
- 122
-
2
-
25All Python objects have the same kind of thread-safeness -- they themselves don't go corrupt, but their data may. collections.deque is what's behind Queue.Queue objects. If you're accessing things from two threads, you really should use Queue.Queue objects. Really. – Thomas Wouters Jun 12 '11 at 00:09
-
10lemiant, deque is thread-safe. From Chapter 2 of Fluent Python: "The class collections.deque is a thread-safe double-ended queue designed for fast inserting and removing from both ends. [...] The append and popleft operations are atomic, so deque is safe to use as a LIFO-queue in multi-threaded applications without the need for using locks." – Al Sweigart Jan 10 '18 at 00:12
-
4Is this answer about CPython or about Python? What's the answer for Python itself? – user541686 Feb 20 '18 at 13:16
-
@Nils: Uh, the first page you linked to says Python instead of CPython because it **is** describing the Python language. And that second link literally says there are multiple implementations of the Python language, just one that happens to be more popular. Given the question was about Python, the answer should describe what can be guaranteed to happen in any conformant implementation of Python, not just what happens in CPython in particular. – user541686 May 10 '19 at 07:58
-
1@Mehrdad Do you want to discuss the topic or not? You said, you dont want to. Seems you changed your mind. Ok. The answer to your question is: The answer for Python is identical to the answer for CPython because that is the reference implementation. Yes, it may be of interest how it is done in other implementations, but your question was *Is this answer about CPython or about Python?* and the answer to this is: BOTH. Python is NOT other implementations. It is just CPython. – Nils Lindemann May 10 '19 at 17:14
To clarify a point in Thomas' excellent answer, it should be mentioned that append()
is thread safe.
This is because there is no concern that data being read will be in the same place once we go to write to it. The append()
operation does not read data, it only writes data to the list.

- 30,064
- 36
- 138
- 197
-
1PyList_Append is reading from memory. Do you mean that its reads and writes happen in the same GIL lock? https://github.com/python/cpython/blob/1cecdbbf1adef9af2ac829c7dbba91352accb1fd/Objects/listobject.c#L303 – amwinter Sep 24 '14 at 13:13
-
1@amwinter Yes, the whole call to `PyList_Append` is done in one GIL lock. It is given a reference to an object to append. That object's contents might be changed after it is evaluated and before the call to `PyList_Append` is done. But it will still be the same object, and safely appended (if you do `lst.append(x); ok = lst[-1] is x`, then `ok` may be False, of course). The code you reference doesn't read from the appended object, except to INCREF it. It reads, and may reallocate, the list which is appended to. – greggo May 09 '16 at 14:24
-
3dotancohen 's point is that `L[0] += x` will perform a `__getitem__` on `L` and then a `__setitem__` on `L` -- if `L` supports `__iadd__` it will do things a bit differently at the object interface, but there are still two separate operations on `L` at the python interpreter level (you will see them in the compiled bytecode). The `append` is done in a a single method call in the bytecode. – greggo May 09 '16 at 14:33
-
1This is helpful. I get that `li.append(item)` is threadsafe, but I suppose that `li += [item]` is *not* threadsafe, correct? – speedplane Feb 07 '17 at 14:53
-
13
-
@greggo that sounds like current CPython implementation, not Python. Does the same hold for PyPy, for example? – Rob Grant May 02 '18 at 14:49
-
@robert-grant My comments are indeed about CPython. I don't know about other implementations. – greggo May 05 '18 at 23:10
Here's a comprehensive yet non-exhaustive list of examples of list
operations and whether or not they are thread safe.
Hoping to get an answer regarding the obj in a_list
language construct here.

- 1,604
- 4
- 20
- 36

- 101,334
- 104
- 266
- 359
-
6Never in a million years would I have expected that list.sort() to be atomic and I was skeptical but I tested it and it's true, as soon as one thread started sorting a huge list containing 1e8 elements it blocked all other threads from accessing the list. (I had another thread constantly retrieving element 0 and it hanged for a couple of seconds while thread A was sorting). So I guess it's true and verified in `pythong 3.9.1` – A Kareem Dec 25 '20 at 16:29
-
It was included in the [Python FAQ](https://docs.python.org/3/faq/library.html#what-kinds-of-global-value-mutation-are-thread-safe) – bartolo-otrit Aug 30 '23 at 09:36
I recently had this case where I needed to append to a list continuously in one thread, loop through the items and check if the item was ready, it was an AsyncResult in my case and remove it from the list only if it was ready. I could not find any examples that demonstrated my problem clearly Here is an example demonstrating adding to list in one thread continuously and removing from the same list in another thread continuously The flawed version runs easily on smaller numbers but keep the numbers big enough and run a few times and you will see the error
The FLAWED version
import threading
import time
# Change this number as you please, bigger numbers will get the error quickly
count = 1000
l = []
def add():
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
for i in range(count):
l.remove(i)
time.sleep(0.0001)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()
print(l)
Output when ERROR
Exception in thread Thread-63:
Traceback (most recent call last):
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "<ipython-input-30-ecfbac1c776f>", line 13, in remove
l.remove(i)
ValueError: list.remove(x): x not in list
Version that uses locks
import threading
import time
count = 1000
l = []
lock = threading.RLock()
def add():
with lock:
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
with lock:
for i in range(count):
l.remove(i)
time.sleep(0.0001)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()
print(l)
Output
[] # Empty list
Conclusion
As mentioned in the earlier answers while the act of appending or popping elements from the list itself is thread safe, what is not thread safe is when you append in one thread and pop in another

- 3,739
- 1
- 35
- 47

- 5,433
- 4
- 57
- 90
-
13The version with locks has the same behavior as the one without the locks. Basically the error is coming because it is trying to remove something which is not in the list, it has nothing to do with thread safety. Try running the version with locks after changing the start order i.e. start t2 before t1 and you will see the same error. whenever t2 gets ahead of t1 the error will occur no matter if you use locks or not. – Dev Nov 08 '19 at 19:10