2009-08-22 73 views
2

我正在嘗試學習Scala,並發現它迄今爲止已經是一門很棒的語言。我從David Pollak學習"Beginning Scala"。在第3章中有一段代碼,它演示瞭如何編寫沒有同步塊的多線程代碼(該代碼是從本書複製的,可以從Apress site下載,我不打算在這裏打破任何規律):這段代碼中是否存在潛在的飢餓,還是僅僅是我?

 
import java.util.concurrent.atomic.{AtomicReference => AtomR, AtomicLong} 
import java.util.Random 
import scala.collection.immutable.TreeHashMap 

object Multics { 
    type MT = Map[String, Int] 

    val info: AtomR[MT] = new AtomR(TreeHashMap.empty) 
    val clashCnt = new AtomicLong 

    def main(argv: Array[String]) { 
    runThread { 
     repeatEvery(1000) { 
     println("Clash Count: "+clashCnt+" Total: "+ 
       info.get.foldLeft(0)(_ + _._2)) 
     } } 

    for (i old + (name -> 0)} 
     repeatEvery(ran.nextInt(100)) { 
     doSet(info){old => old + (name -> (old(name) + 1))} 
     cnt = cnt + 1 
     if (cnt != info.get()(name)) 
      throw new Exception("Thread: "+name+" failed") 
     } } 
    } 

    def runThread(f: => Unit) = 
    (new Thread(new Runnable {def run(): Unit = f})).start 

    def doSet[T](atom: AtomR[T])(update: T => T) { 
    val old = atom.get 
    if (atom.compareAndSet(old, update(old)))() 
    else { 
     clashCnt.incrementAndGet 
     doSet(atom)(update) 
    } 
    } 

    def repeatEvery(len: => Int)(body: => Unit): Unit = { 
    try { 
     while(true) { 

     Thread.sleep(len) 
     body 
     } 
    } catch { 
     case e => e.printStackTrace; System.exit(1) 
    } 
    } 
} 

據我瞭解,有在功能doSet潛在飢餓問題(不吉利的線程可能總是發生衝突,並導致StackOverflowException)。我是否對,如果是這樣,那麼如何改進這個代碼來解決這個問題呢?

+0

你的代碼肯定是不完整的(一個無用的東西就是它不能編譯的事實;-)。請用http://uys.be/Multics.scala替換它(我沒有足夠的點來編輯問題)。 – opyate 2009-12-05 23:51:06

回答

2

首先,我認爲你從本書例子中粘貼的代碼的大部分缺失;這讓你很難理解你的問題。其次,確實可以遞歸地調用doSet()多次,但是在這種情況下不會發生StackOverflowException,因爲一次保存寬限期:尾遞歸。 doSet()在函數的末尾被遞歸地調用(在遞歸調用之後不再進行處理),並且不能被重寫(在對象內定義),其限定它優化成循環而沒有任何堆棧開銷。

在2.8.0中,有一個名爲@ scala.annotation.tailrec的註釋,它在def上使用時將確保def內的遞歸調用確實是尾遞歸。

+0

整個代碼被粘貼,你只需要滾動它。 – 2009-08-22 18:18:41

+0

如果我正確理解你的答案,飢餓在這裏是可能的(儘管極不可能),它會導致無限循環,即使通過StackOverflowException也不會停止? – 2009-08-22 18:26:16

+0

我在說的是doSet()可能會被遞歸調用多次,但由於不涉及堆棧幀的消耗,所以沒有堆棧溢出的危險。至於線程匱乏,它實際上取決於你對飢餓的定義。有了今天的硬件,我無法真正看到2000年以來的任何線程開始捱餓超過幾秒鐘。 – 2009-08-23 06:18:32

0

它看起來像使用比較和交換(http://en.wikipedia.org/wiki/Compare-and-swap)而不是鎖定。

這種方法的一般想法是,你循環,直到你設置的值始終如一。

我不知道如何解決飢餓問題。我的猜測是理論上存在飢餓問題,但在實踐中它會沒事的。

相關問題