谁能解释有趣的自旋锁行为?

时间:2018-05-05 19:09:57

标签: scala concurrency spinlock

给出以下代码

case class Score(value: BigInt, random: Long = randomLong) extends Comparable[Score] {

  override def compareTo(that: Score): Int = {
    if (this.value < that.value) -1
    else if (this.value > that.value) 1
    else if (this.random < that.random) -1
    else if (this.random > that.random) 1
    else 0
  }

  override def equals(obj: _root_.scala.Any): Boolean = {
    val that = obj.asInstanceOf[Score]
    this.value == that.value && this.random == that.random
  }
}

@tailrec
private def update(mode: UpdateMode, member: String, newScore: Score, spinCount: Int, spinStart: Long): Unit = {
  // Caution: there is some subtle logic below, so don't modify it unless you grok it

  try {
    Metrics.checkSpinCount(member, spinCount)
  } catch {
    case cause: ConcurrentModificationException =>
      throw new ConcurrentModificationException(Leaderboard.maximumSpinCountExceeded.format("update", member), cause)
  }

  // Set the spin-lock
  put(member, None) match {
    case None =>
      // BEGIN CRITICAL SECTION
      // Member's first time on the board
      if (scoreToMember.put(newScore, member) != null) {
        val message = s"$member: added new member in memberToScore, but found old member in scoreToMember"
        logger.error(message)
        throw new ConcurrentModificationException(message)
      }
      memberToScore.put(member, Some(newScore)) // remove the spin-lock
      // END CRITICAL SECTION
    case Some(option) => option match {
      case None =>            // Update in progress, so spin until complete
        //logger.debug(s"update: $member locked, spinCount = $spinCount")
        for (i <- -1 to spinCount * 2) {Thread.`yield`()} // dampen contention
        update(mode, member, newScore, spinCount + 1, spinStart)
      case Some(oldScore) =>
        // BEGIN CRITICAL SECTION
        // Member already on the leaderboard
        if (scoreToMember.remove(oldScore) == null) {
            val message = s"$member: oldScore not found in scoreToMember, concurrency defect"
            logger.error(message)
            throw new ConcurrentModificationException(message)
          } else {
            val score =
              mode match {
                case Replace =>
                  //logger.debug(s"$member: newScore = $newScore")
                  newScore
                case Increment =>
                  //logger.debug(s"$member: newScore = $newScore, oldScore = $oldScore")
                  Score(newScore.value + oldScore.value)
              }
            //logger.debug(s"$member: updated score = $score")
            scoreToMember.put(score, member)
            memberToScore.put(member, Some(score))  // remove the spin-lock
            //logger.debug(s"update: $member unlocked")
          }
        // END CRITICAL SECTION
        // Do this outside the critical section to reduce time under lock
        if (spinCount > 0) Metrics.checkSpinTime(System.nanoTime() - spinStart)
    }
  }
}

有两个重要的数据结构:memberToScore和scoreToMember。我已尝试对memberToScore使用TrieMap[String,Option[Score]]ConcurrentHashMap[String,Option[Score]],两者都具有相同的行为。

到目前为止,我的测试表明代码是正确的并且线程安全,但神秘之处在于自旋锁的性能。在具有12个硬件线程的系统上,以及12个Futures上的1000次迭代:一直击中相同的成员导致旋转周期为50或更多,但是随机分布的成员可以导致旋转周期为100或更多。如果我不在没有迭代yield()调用的情况下抑制旋转,行为会变得更糟。

所以,这似乎是反直觉的,我期待键的随机分布导致比同一键更少的旋转,但测试证明不是。

任何人都可以对这种反直觉行为提供一些见解吗?

当然,对我的设计可能有更好的解决方案,我对他们持开放态度,但现在我似乎找不到令人满意的解释,我的测试显示了什么,而我的好奇心让我感到饥饿。

另外,虽然单一成员测试的旋转计数上限较低,但随机成员测试的时间旋转上限较低,这正是我所期望的。我无法解释为什么随机成员测试通常会产生更高的旋转计数上限。

0 个答案:

没有答案