2016-08-16 1 views
2

Я прийти через функциональность, которая требуется следующий шаблон:Как защитить объект, используя блокировку в Python?

from threading import Lock 

the_list = [] 
the_list_lock = Lock() 

и использовать его:

with the_list_lock: 
    the_list.append("New Element") 

К сожалению, это не требует, чтобы я приобрести замок, я мог бы просто получить доступ к объект напрямую. Мне нужна защита от этого (я только человек). Есть ли стандартный способ сделать это? Мой собственный подход заключается в создании HidingLock класса, который может быть использован, как это:

the_list = HidingLock([]) 
with the_list as l: 
    l.append("New Element") 

Но он чувствует себя настолько основополагающим, что либо он должен существовать в стандартной библиотеке, или это очень нетрадиционный способ использования замков.

+0

Значение требует, чтобы замок приобретается, прежде чем что-то доступ? Не совсем то, что у вас есть, - это ожидаемое решение, создайте для него интерфейс (т. Е. Класс). Вместо прямого доступа к списку вы переходите через интерфейс класса. – danny

+0

Как далеко вы хотите защитить доступ? Что делать, если объект передается в другой поток или продолжается доступ после завершения блока? – Dunes

+0

@ Dunes: Просто против случайного забвения. Я не думаю, что можно сделать больше, чем в Python. –

ответ

4

Я думаю, что причина нет ничего стандартная библиотека - потому что для того, чтобы она была там, ей нужно было бы получить гарантии доступа к чугуну. Чтобы обеспечить что-либо меньшее, вы получите ложное чувство безопасности, которое может привести к решению многих параллельных проблем.

Также почти невозможно сделать эти гарантии, не принося существенных жертв. Таким образом, пользователю остается решить, как они будут управлять проблемами параллелизма. Это соответствует одной из философов Python: «Мы все соглашаемся с взрослыми». То есть, если вы пишете класс, я думаю, что разумно, что вы должны знать, какие атрибуты вам нужны, чтобы получить блокировку перед доступом к атрибуту. Или, если вы действительно так обеспокоены, напишите класс wrapper/proxy, который контролирует весь доступ к базовому объекту.

В вашем примере существует несколько способов, с помощью которых целевой объект мог случайно уйти. Если программист не обращает достаточного внимания на код, который они пишут/поддерживает, то этот HiddenLock может обеспечить это ложное чувство безопасности. Например:

with the_lock as obj: 
    pass 
obj.func() # erroneous 

with the_lock as obj: 
    return obj.func() # possibly erroneous 
# What if the return value of `func' contains a self reference? 

with the_lock as obj: 
    obj_copy = obj[:] 

obj_copy[0] = 2 # erroneous? 

Этот последний особенно пагубный. Является ли этот код безопасным потоком, зависит не от кода внутри блока с блоком, либо даже от кода после блока. Вместо этого это реализация класса obj, который будет означать, что этот код является потокобезопасным или нет. Например, если obj является list, тогда это безопасно, так как obj[:] создает копию. Однако, если obj является numpy.ndarray, тогда obj[:] создает представление, и поэтому операция небезопасна.

Фактически, если содержимое obj было изменчивым, то это может быть небезопасным как независимо (например, obj_copy[0].mutate()).

+0

Я понимаю ваш аргумент, но все же гораздо легче стрелять в ногу, имея только замки. Некоторые из проблем, которые вы упомянули, могут быть даже исправлены, если вы обнаружите объект-прокси вместо реального объекта. –

0

Мое текущее решение (которое я говорю в вопросе) выглядит следующим образом:

import threading 

class HidingLock(object): 
    def __init__(self, obj, lock=None): 
     self.lock = lock or threading.RLock() 
     self._obj = obj 

    def __enter__(self): 
     self.lock.acquire() 
     return self._obj 

    def __exit__(self, exc_type, exc_value, traceback): 
     self.lock.release() 

    def set(self, obj): 
     with self: 
      self._obj = obj 

и вот как можно было бы использовать:

locked_list = HidingLock(["A"]) 
with locked_list as l: 
    l.append("B") 
+0

в 'set', что является использованием условия if. Учитывая, что Thread1 приобрел блокировку и Thread2, называемый set без получения, условие if будет по-прежнему истинным, и Thread2 будет разрешено изменять _obj. – Akilesh

+0

Я редактировал код для использования блокировки реентера. Теперь любой поток может просто вызвать набор, и только один поток, который приобрел блокировку, должен быть изменен. _obj – Akilesh

+0

@Akilesh: Я тоже об этом думал. Однако * если * каждое использование 'HidingLock.set' обернуто внутри блока' with', использование 'RLock' не требуется, потому что при вызове' set() 'блокировка будет получена. –

0

Что о создании shared_list, который имеет list и реализует желаемые методы класса с использованием threading.Lock:

import threading 

class SharedList(object): 
    def __init__(self, iterable=None): 
     if iterable is not None: 
      self.list = list(iterable) 
     else: 
      self.list = list() 
     self.lock = threading.Lock() 
     self.index = None 

    def append(self, x): 
     with self.lock: 
      self.list.append(x) 

    def __iter__(self): 
     shared_iterator = SharedList() 
     shared_iterator.list = self.list 
     shared_iterator.lock = self.lock 
     shared_iterator.index = 0 
     return shared_iterator 

    def next(self): 
     with self.lock: 
      if self.index < len(self.list): 
       result = self.list[self.index] 
       self.index += 1 
      else: 
       raise StopIteration 
      return result 

    # Override other methods 

if __name__ == '__main__': 
    shared_list = SharedList() 
    for x in range(1, 4): 
     shared_list.append(x) 
    for entry in shared_list: 
     print entry 

Выход

1 
2 
3 

Как Georg Shölly отметил в комментариях, это будет требуют много работы для реализации каждого метода. Однако, если все, что вам нужно, это список, который вы можете добавить, а затем перебрать, этот пример обеспечивает отправную точку.

Тогда вы можете просто написать

the_list = SharedList() 
the_list.append("New Element") 
+0

Сложно переопределить все атрибуты, которые я бы сказал. И даже если бы мы могли, такие методы, как '__iter __()', не являются тривиальными, чтобы сделать потокобезопасным. –