2017-08-26 41 views
0

以下簡單的代碼應該,據我所知,總是打印出'0'。但是,使用「lock = True」運行時,通常會打印出其他正數或負數。Python多處理共享變量不穩定的行爲

import multiprocessing as mp 
    import sys 
    import time 

    num = mp.Value('d', 0.0, lock = False) 


    def func1(): 
     global num 
     print ('start func1') 
     #While num.value < 100000: 
     for x in range(1000): 
      num.value += 1 
      #print(num.value) 
     print ('end func1') 

    def func2(): 
     global num 
     print ('start func2') 
     #while num.value > -10000: 
     for x in range(1000): 
      num.value -= 1 
      #print(num.value) 
     print ('end func2') 

if __name__=='__main__': 
    ctx = mp.get_context('fork') 
    p1 = ctx.Process(target=func1) 
    p1.start() 
    p2 = ctx.Process(target=func2) 
    p2.start() 
    p1.join() 
    p2.join() 
    sys.stdout.flush() 
    time.sleep(25) 
    print(num.value) 

任何人都可以提供任何解釋嗎?

澄清:當鎖定設置爲「False」時,它的行爲與預期的一樣,打印出'0',但是當它爲'True'時通常不會。

這對於更大的「範圍」值更爲常見/更常見。

使用python 3.6在兩個平臺(Mac OSx和Ubuntu 14.04.01)上進行了測試。

+0

你使用的是什麼版本的python平臺?這兩種情況都像預期的那樣:https://ideone.com/JoBXwZ –

+0

這是從您發佈的鏈接輸出:開始FUNC2 結束FUNC2 開始FUNC1 結束FUNC1 124.0 如果最終數量不爲0?這是如何預期的? – MHH

+0

我已經運行了大約10次,最後總是得到0.0。我們必須生活在一個平行的宇宙中。 :D –

回答

0

docsmultiprocessing.Value都對此很明確:

操作,如+ =其中涉及讀取和寫入不是原子。所以,如果,例如,你想原子方式增加一個共同的價值是不夠的只是做

counter.value += 1 

假設相關的鎖是遞歸的(這是默認設置),則可以改爲做

with counter.get_lock(): 
    counter.value += 1 

對於您的評論,這不是「1000增量」。這是1000次迭代:

# Take lock on num.value 
temp_value = num.value # (1) 
# release lock on num.value (anything can modify it now) 
temp_value += 1   # (2) 
# Take lock on num.value 
num.value = temp_value # (3) 
# release lock on num.value 

這時候,它說+=不是原子是什麼意思。

如果在第2行期間num.value被另一個進程修改,那麼第3行會將錯誤的值寫入num.value


爲了讓一個更好的方式爲例來接近你在做什麼,這是一個使用隊列,確保一切都保持滴答滴答步調一致版本:

import multiprocessing as mp 
import queue 
import sys 


# An increment process. Takes a value, increments it, passes it along 
def func1(in_queue: mp.Queue, out_queue: mp.Queue): 
    print('start func1') 

    for x in range(1000): 
     n = in_queue.get() 
     n += 1 
     print("inc", n) 
     out_queue.put(n) 
    print('end func1') 


# An decrement process. Takes a value, decrements it, passes it along 
def func2(in_queue: mp.Queue, out_queue: mp.Queue): 
    print('start func2') 
    for x in range(1000): 
     n = in_queue.get() 
     n -= 1 
     print("dec", n) 
     out_queue.put(n) 
    print('end func2') 


if __name__ == '__main__': 
    ctx = mp.get_context('fork') 

    queue1 = mp.Queue() 
    queue2 = mp.Queue() 

    # Make two processes and tie their queues back to back. They hand a value 
    # back and forth until they've run their course. 
    p1 = ctx.Process(target=func1, args=(queue1, queue2,)) 
    p1.start() 
    p2 = ctx.Process(target=func2, args=(queue2, queue1,)) 
    p2.start() 

    # Get it started 
    queue1.put(0) 

    # Wait from them to finish 
    p1.join() 
    p2.join() 

    # Since this is a looping process, the result is on the queue we put() to. 
    # (Using block=False because I'd rather throw an exception if something 
    # went wrong rather than deadlock.) 
    num = queue1.get(block=False) 

    print("FINAL=%d" % num) 

這是一個非常簡單的例子。在更健壯的代碼中,您需要考慮失敗情況下發生的情況。例如,如果p1引發異常,則p2將等待其值的死鎖。在很多方面,這是一件好事,因爲這意味着您可以通過使用相同的隊列啓動新的p1進程來恢復系統。如果您想進一步研究,這種處理併發性的方法稱爲Actor model

+0

嗯,是的,但是,最終是不是應該在經過1000次遞增和1000次遞減之後,不管這種情況是否回到0? – MHH

+0

另外,這隻發生在參數'lock = true'而不是'lock = false'時對我沒有意義。 – MHH

+0

當'lock = False'時,你可能會很幸運,因爲操作速度更快(沒有「鎖定」步驟)。競賽狀況依然存在;你只是沒有看到它,因爲它顯着更小。取鎖是一項非常緩慢的操作。當你沒有鎖的時候,你的平臺上的共享內存也允許實現+ =的原子增量(我必須研究源代碼以查看它們是否正在使用它)。即使是真的,依靠它也是非常危險的。 –