`with`语句__enter__和__exit__线程是否安全?

时间:2016-06-29 01:53:40

标签: python thread-safety with-statement

假设:

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
  1. 多线程可能会self.cnt += 1执行两次吗?
  2. 对于相同的上下文管理器实例,有可能在多线程中,以某种方式__enter__被调用两次而__exit__只被调用一次,因此self.cnt最终结果为{{1} }}?

1 个答案:

答案 0 :(得分:2)

不,只能通过锁来保证线程安全。

  

多线程可能会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?