`with` 语句 __enter__ 和 __exit__ 线程安全吗?
Is `with` statement __enter__ and __exit__ thread safe?
假设:
class A(object):
def __init__(self):
self.cnt = 0
def __enter__(self):
self.cnt += 1
def __exit__(self, exc_type, exc_value, traceback)
self.cnt -= 1
- 多线程的时候
self.cnt += 1
会不会执行两次?
- 是否有可能对于同一个上下文管理器实例,在多线程中,不知何故
__enter__
被调用两次而 __exit__
只被调用一次,所以 self.cnt
最终结果是 1
?
不行,线程安全只能通过锁来保证。
Is it possible that self.cnt += 1
might be executed twice when multi-threading?
如果你有两个线程运行那,它会被执行两次。三个线程,三次,等等。我不确定你的真正意思,也许告诉我们你是如何 building/executing 这些线程与你的上下文管理器的关系。
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?
是的,最终结果可以是非零的,但不是通过你假设的非对称调用进入和退出的机制。如果您在多个线程中使用相同的上下文管理器实例,您可以构建一个可以重现错误的简单示例,如下所示:
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?
假设:
class A(object):
def __init__(self):
self.cnt = 0
def __enter__(self):
self.cnt += 1
def __exit__(self, exc_type, exc_value, traceback)
self.cnt -= 1
- 多线程的时候
self.cnt += 1
会不会执行两次? - 是否有可能对于同一个上下文管理器实例,在多线程中,不知何故
__enter__
被调用两次而__exit__
只被调用一次,所以self.cnt
最终结果是1
?
不行,线程安全只能通过锁来保证。
Is it possible that
self.cnt += 1
might be executed twice when multi-threading?
如果你有两个线程运行那,它会被执行两次。三个线程,三次,等等。我不确定你的真正意思,也许告诉我们你是如何 building/executing 这些线程与你的上下文管理器的关系。
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?
是的,最终结果可以是非零的,但不是通过你假设的非对称调用进入和退出的机制。如果您在多个线程中使用相同的上下文管理器实例,您可以构建一个可以重现错误的简单示例,如下所示:
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?