这段代码中是否存在潜在的饥饿或仅仅是我?

时间:2009-08-22 14:26:31

标签: multithreading scala

我正在努力学习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)。我是对的,如果是的话,如何改进这些代码来解决这个问题呢?

2 个答案:

答案 0 :(得分:3)

首先,我认为您从书籍示例中粘贴的代码遗漏了一大堆;这让你很难理解你的问题。

其次,doSet()可能会被递归多次调用,但在这种情况下不会发生StackOverflowException,因为有一个保存宽限:尾递归。 doSet()在函数末尾递归调用(在递归调用之后不再进行处理)并且不能被覆盖(在对象内定义),这使得它被优化为循环而没有任何堆栈开销。 / p>

在2.8.0中,有一个名为@scala.annotation.tailrec的注释,当在def上使用时,它将确保def内的递归调用确实是尾递归。

答案 1 :(得分:0)

它看起来像是使用Compare和Swap(http://en.wikipedia.org/wiki/Compare-and-swap)而不是锁定。

这种方法的一般想法是循环,直到你设法一致地设置值。

如果有饥饿问题我还不知道这个问题。我的猜测是理论上存在饥饿问题,但在实践中它会没问题。