2013-11-03 59 views
1

在Redis的更新變量的自由競爭條件,方法是:如何將其作爲上下文管理器來編寫?

r = redis.Redis() 
with r.pipeline() as p: 
    while 1: 
     try: 
      p.watch(KEY) 
      val = p.get(KEY) 
      newval = int(val) + 42 
      p.multi() 
      p.set(KEY, newval) 
      p.execute() # raises WatchError if anyone else changed KEY 
      break 
     except redis.WatchError: 
      continue # retry 

這是顯著比直截了當的版本更加複雜(其中包含一個競爭條件):

r = redis.Redis() 
val = r.get(KEY) 
newval = int(val) + 42 
r.set(KEY, newval) 

所以我想一個上下文管理器將使這更容易的工作,但是,我有問題...

我最初的想法是

with update(KEY) as val: 
    newval = val + 42 
    somehow return newval to the contextmanager...? 

有沒有做最後一行一個明顯的方式,所以我想::

@contextmanager 
def update(key, cn=None): 
    """Usage:: 

      with update(KEY) as (p, val): 
       newval = int(val) + 42 
       p.set(KEY, newval) 

    """ 
    r = cn or redis.Redis() 
    with r.pipeline() as p: 
     while 1: 
      try: 
       p.watch(key) # --> immediate mode 
       val = p.get(key) 
       p.multi() # --> back to buffered mode 
       yield (p, val) 
       p.execute() # raises WatchError if anyone has changed `key` 
       break # success, break out of while loop 
      except redis.WatchError: 
       pass # someone else got there before us, retry. 

其巨大隻要工程,我沒有趕上WatchError,然後我得到

File "c:\python27\Lib\contextlib.py", line 28, in __exit__ 
    raise RuntimeError("generator didn't stop") 
RuntimeError: generator didn't stop 

我做錯了什麼?

+0

爲什麼不是INCRBY 42? –

+0

因爲真正的代碼需要'newval'爲'max(float(val或'0.0')+ 7,time.time())':-) – thebjorn

+0

這並不真正映射到上下文管理器,使用功能(見poke的答案)。 –

回答

5

我認爲問題在於您產生多次(當任務重複時),但上下文管理器只輸入一次(yield僅僅是__enter__方法的語法糖)。所以一旦收益率可以多次執行,就會出現問題。

我不完全確定如何以良好的方式解決這個問題,而且我也無法測試它,所以我只提供一些建議。

首先,我會避免產生相當內部的p;你應該產生一些專門用於更新過程的對象。例如這樣的事情:

with update(KEY) as updater: 
    updater.value = int(updater.original) + 42 

當然,這仍然沒有解決多重收益,正如你不會有在該點的原始值要麼你就不能遷就該對象前面。相反,我們可以指定一個負責值更新的委託。

with update(KEY) as updater: 
    updater.process = lambda value: value + 42 

這將存儲產生對象,然後你可以使用上下文管理器裏面不斷嘗試,直到它成功更新裏面的值的函數。在進入while循環之前,您可以儘早從上下文管理器中獲取更新程序。

當然,如果你已經做到了這一點,實際上沒有任何需要留下一個上下文管理器。相反,你可以做一個功能:

update(key, lambda value: value + 42) 
+0

在[doc](https://pypi.python。org/pypi/redis /),看起來'r.transaction'函數或多或少會這樣做。 –