2016-06-29 5 views
2

Пусть:Является ли `with` statement __enter__ и __exit__ потоком безопасным?

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?
+0

Что вы подразумеваете под тем же экземпляром в многопоточном режиме? Независимо, если вы даете каждому потоку собственный экземпляр любого контекстного менеджера, который вы, возможно, используете, это не будет иметь отношения к делу. Если доступ к данным осуществляется совместно между потоками, доступ к ним должен управляться с помощью мьютексов/блокировок. – metatoaster

+0

@metatoaster Я согласен, каждый поток имеет свой собственный contextmanager, но мой коллега говорит, что '__enter__' - это всего лишь метод экземпляра, он может быть вызван дважды, поэтому он не является потокобезопасным, поэтому тот же самый экземпляр экземпляра contextmanager' __enter__' не является потоком безопасно. Я чувствую себя потерянным – est

+1

Если каждый поток имеет свой собственный экземпляр, тогда нет общих данных. – martineau

ответ

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?

+0

Если мы не используем общий контекст, измените 'с shared_context:' на 'с Context()', может ли cnt по-прежнему быть больше, чем '1'? – est

+0

Еще один вопрос с вашим примером кода, будет ли конечный результат (я имею в виду 'joinall()') 'shared_context.cnt' по-прежнему гарантированно равным нулю? – est

+0

@est: вы можете просто изменить его и убедиться сами.Если вы использовали 'с Context()', то внутри текущего кода не будет общих данных между потоками. Нет вызова 'joinall()' (если вы не имеете в виду для всех потоков, присоединяйтесь к ним), но результаты против 'shared_context.cnt', которые я включил, показывают, что он не гарантированно равен нулю - это может быть _anything_. – metatoaster