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?