I want to create a non-thread-safe chunk of code for experimentation, and those are the functions that 2 threads are going to call.
c = 0
def increment():
c += 1
def decrement():
c -= 1
Is this code thread safe?
If not, may I understand why it is not thread safe, and what kind of statements usually lead to non-thread-safe operations.
If it is thread-safe, how can I make it explicitly non-thread-safe?
No, this code is absolutely, demonstrably not threadsafe.
import threading
i = 0
def test():
global i
for x in range(100000):
i += 1
threads = [threading.Thread(target=test) for t in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
assert i == 1000000, i
fails consistently.
i += 1 resolves to four opcodes: load i, load 1, add the two, and store it back to i. The Python interpreter switches active threads (by releasing the GIL from one thread so another thread can have it) every 100 opcodes. (Both of these are implementation details.) The race condition occurs when the 100-opcode preemption happens between loading and storing, allowing another thread to start incrementing the counter. When it gets back to the suspended thread, it continues with the old value of "i" and undoes the increments run by other threads in the meantime.
Making it threadsafe is straightforward; add a lock:
#!/usr/bin/python
import threading
i = 0
i_lock = threading.Lock()
def test():
global i
i_lock.acquire()
try:
for x in range(100000):
i += 1
finally:
i_lock.release()
threads = [threading.Thread(target=test) for t in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
assert i == 1000000, i
(note: you would need global c in each function to make your code work.)
Is this code thread safe?
No. Only a single bytecode instruction is ‘atomic’ in CPython, and a += may not result in a single opcode, even when the values involved are simple integers:
>>> c= 0
>>> def inc():
... global c
... c+= 1
>>> import dis
>>> dis.dis(inc)
3 0 LOAD_GLOBAL 0 (c)
3 LOAD_CONST 1 (1)
6 INPLACE_ADD
7 STORE_GLOBAL 0 (c)
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
So one thread could get to index 6 with c and 1 loaded, give up the GIL and let another thread in, which executes an inc and sleeps, returning the GIL to the first thread, which now has the wrong value.
In any case, what's atomic is an implementation detail which you shouldn't rely on. Bytecodes may change in future versions of CPython, and the results will be totally different in other implementations of Python that do not rely on a GIL. If you need thread safety, you need a locking mechanism.
To be sure I recommend to use a lock:
import threading
class ThreadSafeCounter():
def __init__(self):
self.lock = threading.Lock()
self.counter=0
def increment(self):
with self.lock:
self.counter+=1
def decrement(self):
with self.lock:
self.counter-=1
The synchronized decorator can also help to keep the code easy to read.
It's easy to prove that your code is not thread safe. You can increase the likelyhood of seeing the race condition by using a sleep in the critical parts (this simply simulates a slow CPU). However if you run the code for long enough you should see the race condition eventually regardless.
from time import sleep
c = 0
def increment():
global c
c_ = c
sleep(0.1)
c = c_ + 1
def decrement():
global c
c_ = c
sleep(0.1)
c = c_ - 1
Short answer: no.
Long answer: generally not.
While CPython's GIL makes single opcodes thread-safe, this is no general behaviour. You may not assume that even simple operations like an addition is a atomic instruction. The addition may only be half done when another thread runs.
And as soon as your functions access a variable in more than one opcode, your thread safety is gone. You can generate thread safety, if you wrap your function bodies in locks. But be aware that locks may be computationally costly and may generate deadlocks.
If you actually want to make your code not thread-safe, and have good chance of "bad" stuff actually happening without you trying like ten thousand times (or one time when you real don't want "bad" stuff to happen), you can 'jitter' your code with explicit sleeps:
def íncrement():
global c
x = c
from time import sleep
sleep(0.1)
c = x + 1
Single opcodes are thread-safe because of the GIL but nothing else:
import time
class something(object):
def __init__(self,c):
self.c=c
def inc(self):
new = self.c+1
# if the thread is interrupted by another inc() call its result is wrong
time.sleep(0.001) # sleep makes the os continue another thread
self.c = new
x = something(0)
import threading
for _ in range(10000):
threading.Thread(target=x.inc).start()
print x.c # ~900 here, instead of 10000
Every resource shared by multiple threads must have a lock.
Are you sure that the functions increment and decrement execute without any error?
I think it should raise an UnboundLocalError because you have to explicitly tell Python that you want to use the global variable named 'c'.
So change increment ( also decrement ) to the following:
def increment():
global c
c += 1
I think as is your code is thread unsafe. This article about thread synchronisation mechanisms in Python may be helpful.
Related
Could there be a race condition in the following code such that two async processes both try to update the global variable B at the same time in the call back function? If so, does python handle this or is it something we have to handle using locks? I have run this piece of code several times and have not had a race condition occur (that I know about) but not sure if that means it is impossible to happen.
Code:
import multiprocessing as mp
ky = 0
B = {}
def update(u):
global B
global ky
B[ky] = u
ky += 1
def square(x, y):
return x*y
def runMlt():
pool = mp.Pool(2)
for i in range(10):
pool.apply_async(square, args=(i, i + 1), callback=update)
pool.close()
pool.join()
if __name__ == '__main__':
runMlt()
No.
The callback is executed by the master process, and changes the master process makes to those variables aren't visible to the subprocesses. No subprocess touches B.
(B doesn't need to be marked global, by the way; you're not assigning to the name, but just mutating it internally.)
Well, there aren't any race conditions because they aren't connected. Remember, these are separate processes with separate address spaces. The subprocesses will inherit the initial values, of the globals, but they are not shared.
If you need to communicate, you need to use something like a Queue to send the results back to "central control".
The multiprocessing module does have shared memory objects.
I have a hypothetical program with two threads.
items = [ 'A', 'B' ]
func1():
while True:
if 'C' not in items:
# do something
func2():
while True:
items.clear()
items.append('A')
items.append('B')
main():
start_new_thread(func1)
start_new_thread(func2)
When not in is executed, I assume Python must internally iterate over the list to check each element. If func2 changes the size of this list during the iteration, will it cause an exception or UB?
You should never allow two threads to access the same piece of memory, at the same time, in any programming language, ever. This creates a data race condition which leads to extremely undefined behavior (If your programming language of choice cannot detect this and throw an exception). The safest thing to do is use a lock object to force the execution of a thread to stop if another thread has acquired the lock, and wait until it is safe to modify the data again. Here is your minimal example with locks added (and also modified to be actual working code).
import threading
items = [ 'A', 'B' ]
lock = threading.Lock()
def func1():
while True:
lock.acquire()
if 'C' not in items:
print("C not in items")
lock.release()
def func2():
while True:
lock.acquire()
items.clear()
items.append('A')
items.append('B')
lock.release()
def main():
thread_a = threading.Thread(target=func1, daemon=True)
thread_b = threading.Thread(target=func2, daemon=True)
thread_a.start()
thread_b.start()
thread_a.join()
thread_b.join()
main()
Note that many libraries may refer to some of their functions as being "thread safe". This means that they already handled any potential data race conditions and you do not have to worry about setting up locks. You may also encounter "atomic" operations, which basically just means that they are thread-safe operations. Atomic operations aren't really a thing in python though.
I have a requirement as below:
global_storage = "some_global_storage_system"
def call_me():
global_storage += 1
if global_storage > 1000:
with threading.Lock():
global_storage = 0 # reset global storage
# performing some action
Above code can be accessed by 5 threads at the same time
I have found a lots of approaches to do so but no where they are collected at one place and it's not mentioned which one of the following is the best and safest approach to implement something like global_storage in above program:
Using python queues library (as it is thread safe, but might be a memory issue) : Put an item in queue every time and check whenever the queue length becomes 1000, then make queue length 0 again.
Using a dictionary at global level (thread safe in Cpython): Make a dictionary, global_dict["count"]=0, and every time update global_dict["count"]++ and then read global_dict["count"]>1000.
Using a global variable, using global keyword (least recommended one by community as it seems)
Using redis(Avoiding as it is an extra burden of network call): redis.set() at the start and redis.get() while getting the value
Using a threading.local object (it feels like the safest one), but then I may need to reduce the count for check to 200 to achieve same results.
You need to realize that the operation global_storage += 1 is probably not an atomic operation in almost any implementation of whatever "some_global_storage_system" you have in mind is. It certainly is not atomic if global_storage is an int, and you can't get much more basic than that. This means that this operation needs to be also serialized under a lock.
In the following code I have created at global scope a threading.Lock instance named global_storage_lock that all threads can access and use to serialize access to global_storage. My only (rhetorical) question to you concerns your comment labeled #performing some action, which you currently have while the lock is acquired. In general, you want to hold a lock for the shortest period possible. If you do not need to be updating global_storage during this action, then perform that action outside of this block where the lock will have been released.
import threading
global_storage_lock = threading.Lock()
global_storage = "some_global_storage_system"
def call_me():
with global_storage_lock:
global_storage += 1
if global_storage > 1000:
global_storage = 0 # reset global storage
# performing some action
Here is an example of how you could handle not requiring the lock to stay acquired when needing to perform an action after resetting global_storage to 0:
import threading
global_storage_lock = threading.Lock()
global_storage = "some_global_storage_system"
def call_me():
action_needed = False
with global_storage_lock:
global_storage += 1
if global_storage > 1000:
global_storage = 0 # reset global storage
action_needed = True
if action_needed:
#performing some action
...
Can someone please tell me if the following is threadsafe or not and if it isnt what must I do to make it so.
Note: this only a small sample, not sure if it runs.
TIMER = True
time_lock = threading.Lock()
def timing():
while TIMER:
# some logic will be here for now print time
print time.time()
timer = threading.Thread(target=timing)
timer2 = threading.Thread(target=timing)
timer.start()
timer2.start()
while True:
time_lock.aquire()
if doSomeStuff():
TIMER = True
if otherThings():
break
time_lock.aquire()
TIMER = False
time_lock.release()
time_lock.aquire()
TIMER = False
time_lock.release()
It depends a bit on which implementation you are using, and what you are doing.
First, in the de facto standard implementation of python ("cpython"), only one thread is allowed to run at a time since some internals of the python interpreter aren't thread-safe. This is controlled by the Global Interpreter Lock (aka "GIL"). So in cpython, you don't really need extra locks at all; the GIL makes sure that only one thread at a time is running python code and possibly changing variables. This is a feature of the implementation, not of the language.
Second, if only one thread writes to a simple variable and others only read it you don't need a lock either, for obvious reasons. It is however up to you as the programmer to make sure that this is the case, and it is easy to make mistakes with that.
Even assinging to a simple variable might not need a lock. In python variables are more like labels used to refer to an object, rather than boxes where you can put something in. So simple assignments are atomic (in the sense that they cannot be interrupted halfway), as you can see when you look at the generated python bytecode:
In [1]: import dis
In [2]: x = 7
In [3]: def setx():
global x
x = 12
...:
In [4]: dis.dis(setx)
3 0 LOAD_CONST 1 (12)
3 STORE_GLOBAL 0 (x)
6 LOAD_CONST 0 (None)
9 RETURN_VALUE
The only code that changes x is the single STORE_GLOBAL opcode. So the variable is either changed or it isn't; there is no inconsistent state inbetween.
But if you e.g. want to test a variable for a certain value, and perform an action while that test still holds true, you do need a lock. Because another thread could have changed the variable just after you tested it.
Things like appending to a list or swapping variables are not atomic. But again in cpython these would be protected by the GIL. In other implementations you'd need a lock around such operations to protect against possible inconsistencies.
If I understand you correctly, you want to signal your thread when to stop. Since this is a situation where only one thread writes to a shared variable and only once, you do not need a lock.
Locks are necessary when you are doing concurrent modification of a shared datastructure that cannot be read atomically.
Is accessing/changing dictionary values thread-safe?
I have a global dictionary foo and multiple threads with ids id1, id2, ... , idn. Is it OK to access and change foo's values without allocating a lock for it if it's known that each thread will only work with its id-related value, say thread with id1 will only work with foo[id1]?
Assuming CPython: Yes and no. It is actually safe to fetch/store values from a shared dictionary in the sense that multiple concurrent read/write requests won't corrupt the dictionary. This is due to the global interpreter lock ("GIL") maintained by the implementation. That is:
Thread A running:
a = global_dict["foo"]
Thread B running:
global_dict["bar"] = "hello"
Thread C running:
global_dict["baz"] = "world"
won't corrupt the dictionary, even if all three access attempts happen at the "same" time. The interpreter will serialize them in some undefined way.
However, the results of the following sequence is undefined:
Thread A:
if "foo" not in global_dict:
global_dict["foo"] = 1
Thread B:
global_dict["foo"] = 2
as the test/set in thread A is not atomic ("time-of-check/time-of-use" race condition). So, it is generally best, if you lock things:
from threading import RLock
lock = RLock()
def thread_A():
with lock:
if "foo" not in global_dict:
global_dict["foo"] = 1
def thread_B():
with lock:
global_dict["foo"] = 2
The best, safest, portable way to have each thread work with independent data is:
import threading
tloc = threading.local()
Now each thread works with a totally independent tloc object even though it's a global name. The thread can get and set attributes on tloc, use tloc.__dict__ if it specifically needs a dictionary, etc.
Thread-local storage for a thread goes away at end of thread; to have threads record their final results, have them put their results, before they terminate, into a common instance of Queue.Queue (which is intrinsically thread-safe). Similarly, initial values for data a thread is to work on could be arguments passed when the thread is started, or be taken from a Queue.
Other half-baked approaches, such as hoping that operations that look atomic are indeed atomic, may happen to work for specific cases in a given version and release of Python, but could easily get broken by upgrades or ports. There's no real reason to risk such issues when a proper, clean, safe architecture is so easy to arrange, portable, handy, and fast.
Since I needed something similar, I landed here. I sum up your answers in this short snippet :
#!/usr/bin/env python3
import threading
class ThreadSafeDict(dict) :
def __init__(self, * p_arg, ** n_arg) :
dict.__init__(self, * p_arg, ** n_arg)
self._lock = threading.Lock()
def __enter__(self) :
self._lock.acquire()
return self
def __exit__(self, type, value, traceback) :
self._lock.release()
if __name__ == '__main__' :
u = ThreadSafeDict()
with u as m :
m[1] = 'foo'
print(u)
as such, you can use the with construct to hold the lock while fiddling in your dict()
The GIL takes care of that, if you happen to be using CPython.
global interpreter lock
The lock used by Python threads to assure that only one thread executes in the CPython virtual machine at a time. This simplifies the CPython implementation by assuring that no two processes can access the same memory at the same time. Locking the entire interpreter makes it easier for the interpreter to be multi-threaded, at the expense of much of the parallelism afforded by multi-processor machines. Efforts have been made in the past to create a “free-threaded” interpreter (one which locks shared data at a much finer granularity), but so far none have been successful because performance suffered in the common single-processor case.
See are-locks-unnecessary-in-multi-threaded-python-code-because-of-the-gil.
How it works?:
>>> import dis
>>> demo = {}
>>> def set_dict():
... demo['name'] = 'Jatin Kumar'
...
>>> dis.dis(set_dict)
2 0 LOAD_CONST 1 ('Jatin Kumar')
3 LOAD_GLOBAL 0 (demo)
6 LOAD_CONST 2 ('name')
9 STORE_SUBSCR
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
Each of the above instructions is executed with GIL lock hold and STORE_SUBSCR instruction adds/updates the key+value pair in a dictionary. So you see that dictionary update is atomic and hence thread safe.