2012-10-23 63 views
2

通常我們在讀取時使用ReadWriteLocks讀取鎖定,並在寫入時寫入鎖定。但一個我認爲反過來使用的奇特案例可以提供幫助。但希望你們能告訴我更好的方法。反向讀取寫入鎖定

這是我想要的。將會有大量的寫入,但少量的讀取。例如,請求的延遲的平均計算器。

幾乎視爲僞代碼。

metric.addValue(latency); // Called a lot. 

metric.getAverage(); // Called sparingly. 

我們可以做到以下幾點:

addValue(value) { 
    atomicCount.increment(); 
    atomicSum.increment(value); 
} 

getAverage() { 
    return atomicCount.get() != 0 ? atomicSum.get()/atomicCount.get() : 0.0; 
} 

的問題是在getAverage(),我們 「可以」 算一些額外的計數。但大多數情況下可能是正確的值,有時還有一個值。但我只是希望它更精確。

這是使用方法:

ReadWriteLock rw = /* write preference, or a fair lock. */; 
Lock read = rw.readLock(); 
Lock write = rw.writeLock(); 

addValue(value) { 
    read.lock(); // Using read lock when mutating. 
    try { 
    atomicCount.increment(); 
    atomicSum.increment(value); 
    } finally { 
    read.unlock(); 
    } 
} 

getAverage() { 
    write.lock(); // Using write lock when reading. 
    try { 
    return atomicCount.get() != 0 ? atomicSum.get()/atomicCount.get() : 0.0; 
    } finally { 
    write.unlock(); 
    } 
} 

我的問題是,我可以做的更好? Salt:我知道(cast)問題,並且多次調用count.get()等可以避免出現更好的性能,但是不想混淆代碼太多。

+1

你知道爲什麼它有多個併發讀取,同時防止併發寫入,但不是相反嗎?我不認爲你這樣做。 –

+1

@MattBall他希望*平均值*是準確的,但要計算它需要同步兩個*原子。如果在兩個gets()之間發生寫操作,calc將會出錯 - 這是他的問題 – Bohemian

+0

您應該將read.unlock()放在finally塊中... – breezee

回答

1

就正確性而言,我認爲你的計劃是一個相當狡猾的計劃。你已經設置好了,所以多個更新線程可以獨立增加計數和總計,因此可以安全地通過讀鎖。

您的平均計算髮生在寫入鎖定下,因此可以保證沒有更新的「讀取器」可以處於活動狀態,從而使計數和總計暫時失去作用。

對我來說最大的問題是你的方案是否真的提供了更好的性能,簡單的同步行爲?雖然您已經通過避免代碼中的同步部分來消除閱讀器之間的表面爭用點,但在讀者/寫者代碼的掩護下,可能會在同步塊中做一些巧妙的事情。見ReadWrite Lock documentation。這也警告說,取決於作家可能遭受飢餓的實施細節。

只有仔細測量才能告訴我們答案。

+0

addValue()花費3 CAS操作,這不可能是好的。 – irreputable

2

您可能想看看像下面這樣的技術是否表現更好。基本上,它保證了數量和金額是通過增加另一個反跟蹤的第一,但畢竟其他值已經完成更新只更新「穩定」,所以沒有鎖都參與:

addValue(value) { 

    while (atomicFlag.get() != 0) { 
     // spin 
    } 
    atomicCount.increment(); 
    atomicSum.increment(value); 
    atomicCount2.increment(); 
} 

getAverage() { 
    int count; 
    int sum; 
    int count2; 

    atomicFlag.increment(); 
    do { 
     count = atomicCount.get(); 
     sum = atomicSum.get(); 
     count2 = atomicCount2.get(); 
    } while (count != count2); 
    atomicFlag.decrement(); 

    return count != 0 ? (sum * 1.0)/count : 0.0; 
} 
+0

薩欽指出有一種競賽。 – atuls

3

真的沒有點用於併發原子增量;無論如何,它們不能同時發生。

最簡單的辦法 - 一個簡單的鎖,普通計數/總和變量 - 將執行好得多

lock 
    count++; 
    sum += value; 
unlock 

更平行,我們需要「分片」 - 每個線程維護其自己的統計信息;讀者全部查詢全部圖片。(每線程統計需要揮發;閱讀器使用邁克爾·伯爾的方法來檢索每個線程統計的穩定版本)

1

我跑了每個解決方案,包括我自己的基準。

唯一的addValue來自100個線程,每100個任務循環,循環,在每個任務10000個更新與值0到9999的結果是:

(irreputable) Just synchronized {}: 7756 ms Average: 4999.5 
(atuls) My reverse read-write lock: 16523 ms Average: 4999.5 
(michael burr) Double counter trick: 10698 Average: 4999.5 
No thread safety: 4115 ms Average: 4685.0 
(atuls) Not thread safe v1. 11189 ms Average: 4999.5 

貌似irreputable是正確的:)

+0

你能提供你的基準代碼嗎?無論是在這個答案本身,或者一個鏈接到pastebin/github /任何。當你說不可信的解決方案時,你的意思是他的第一個鎖定/解鎖,而不是每個線程保持自己的統計數據的解決方案,對吧?另外,你是否有理由解釋爲什麼你的非線程安全版本也能得到正確答案? – ShreevatsaR

+0

https://code.google.com/p/java-nuggets/source/browse/sharedlocks/trunk/src/test/java/com/inmobi/sharedlock/BenchMarkTest.java – atuls

2

(從G +複製討論)。

一種優化的想法是用的AtomicLong存儲在價值和龍,由我們解決,同時計算平均確保計和價值相匹配的問題,不同的位置計數。

另一個(較大)的優化是使用線程特定度量(如irreputable早先提出)。它具有以下優點。

  • 它避免了寫入時的任何形式的爭用。所以CAS寫入速度會很快,因爲沒有其他線程正在寫入相同的指標。
  • 閱讀不需要任何鎖。
  • 最重要的是,它會更好地利用L1緩存的。

說明的最後一點:

當有多個線程做大量的寫入&從一個單一的共享內存中讀取,多核CPU中,線程在不同內核上運行將只保留使其他核心L1緩存無效。因此,必須使用緩存一致性協議從其他核心獲取最新值。所有這些都會大大減慢速度。具有線程特定度量可以避免這個問題。

參考: http://www.cs.washington.edu/education/courses/cse378/07au/lectures/L25-Atomic-Operations.pdf

考慮到這一點這樣的代碼將表現良好。

private final AtomicLongMap<Long> metric = AtomicLongMap.create(); 

public void addValue(long value) { 
    long threadId = Thread.currentThread().getId(); 
    metric.addAndGet(threadId, (value << 32) + 1); 
} 

public synchronized double getAverage() { 
    long value = metric.sum(); 
    int count = (int)value; 
    return (count == 0) ? 0 : ((double)(value >> 32))/count; 
} 

實際上,測試表明它的性能最好 - 比上面的無鎖解決方案更好!而且數量級也是如此。

No thread safety: 3435ms, Average: 1.3532233016178474 
(irreputable) Just synchronized {} 4665ms, Average: 4.0 
(atuls) reverse read-write lock: 19703ms, Average: 4.0 
(michael burr) 17150ms, Average: 4.0 
(therealsachin) 1106ms, Average: 4.0