No, thread safety can only be guaranteed through locks.
Is it possible that self.cnt += 1
might be executed twice when multi-threading?
If you have two threads running that, it will be executed twice. Three threads, thrice, etc. I am not sure what you really mean by this, perhaps show us how you are building/executing these threads with relation to your context manager.
Is it possible that for the same context manager instance, in multithreading, somehow __enter__
be called twice and __exit__
be called only once, so the self.cnt final result is 1?
Yes, final result can be non-zero, but not through the mechanism that you are assuming with asymmetric calling of enter and exit. If you use the same context manager instance across multiple threads, you can construct a simple example that can reproduce errors like so:
from threading import Thread
class Context(object):
def __init__(self):
self.cnt = 0
def __enter__(self):
self.cnt += 1
def __exit__(self, exc_type, exc_value, traceback):
self.cnt -= 1
shared_context = Context()
def run(thread_id):
with shared_context:
print('enter: shared_context.cnt = %d, thread_id = %d' % (
shared_context.cnt, thread_id))
print('exit: shared_context.cnt = %d, thread_id = %d' % (
shared_context.cnt, thread_id))
threads = [Thread(target=run, args=(i,)) for i in range(1000)]
# Start all threads
for t in threads:
t.start()
# Wait for all threads to finish before printing the final cnt
for t in threads:
t.join()
print(shared_context.cnt)
You will inevitably find that the final shared_context.cnt
often do not end up back at 0
, even though when all the threads have started and finished with the exact same code, even though enter and exit have all been called more or less in pairs:
enter: shared_context.cnt = 3, thread_id = 998
exit: shared_context.cnt = 3, thread_id = 998
enter: shared_context.cnt = 3, thread_id = 999
exit: shared_context.cnt = 3, thread_id = 999
2
...
enter: shared_context.cnt = 0, thread_id = 998
exit: shared_context.cnt = 0, thread_id = 998
enter: shared_context.cnt = 1, thread_id = 999
exit: shared_context.cnt = 0, thread_id = 999
-1
This is mostly caused by the +=
operator being resolved to four opcodes and only individual opcodes are guaranteed to be safe if only by the GIL. More details can be found at this question: Is the += operator thread-safe in Python?