不,线程安全只能通过锁来保证。
多线程时self.cnt += 1会不会被执行两次?
如果你有两个线程运行它,它将被执行两次。三个线程,三次等。我不确定您的真正意思是什么,也许可以向我们展示您如何构建/执行与上下文管理器相关的这些线程。
是否有可能对于同一个上下文管理器实例,在多线程中,__enter__ 被调用两次,__exit__ 只被调用一次,所以 self.cnt 最终结果是 1?
是的,最终结果可以是非零的,但不是通过您假设的非对称调用进入和退出的机制。如果您跨多个线程使用相同的上下文管理器实例,您可以构建一个可以重现错误的简单示例,如下所示:
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)
你会不可避免地发现最终的shared_context.cnt 通常不会回到0,即使所有线程都以完全相同的代码启动和结束,即使进入和退出都被调用了更多成对或更少:
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
这主要是由于+= 运算符被解析为四个操作码,并且只有通过 GIL 才能保证单个操作码是安全的。更多细节可以在这个问题中找到:Is the += operator thread-safe in Python?