読者です 読者をやめる 読者になる 読者になる

osiire’s blog

ふしぎなそふとやさん

Alloy model of lock-free stack

唐突にlock-freeをAlloyで書いてみたくなったので書いた。

what is lock-free

kumagiさんの資料によると、lock-free stackはリンクリストみたいなもので、先頭へのポインタをCAS(Compare and Swap)で切り替えるらしい。
素直にAlloy化するとこうなる。

sig Data {}

// linked elements of stack.
sig StackElem {
  data:Data
, next:lone StackElem // edge dosen't have next element.
} {
  no next & this // prevent self pointing.
}

sig HeadPointer { // HeaderPointer indicates the first stack element
  point: disj StackElem
}

このスタックの先頭を書き換えるPush操作を定義しなければならないが、今回はこれをfという3項関係で表現してみた。
元のポインタから次の新しいポインタへ書き換えることを表現している。

sig Push {
  newData:Data
, f : HeadPointer -> HeadPointer //old pointer replaces to the new one.
} {
 one f
}
fact UniqPush {
  all x,y:Push | x.f = y.f <=> x = y
}

// the rule of push operation.
// Push Behavior
// HeaderPointer-(point)->StackElem2 -(next)-> StackElem1
//      push a data ↓
// HeaderPointer-(point)->StackElem3 -(next)-> StackElem2 -(next)-> StackElem1
pred pushOperation[w:Push, p, p':HeadPointer] { 
    w.f = p -> p'
    p'.point.data = w.newData
    p'.point.next = p.point  // new
    no p & p'
}

fact {
  // Push.f satisfy pushOperation rule.
  all w:Push | one p, p':HeadPointer | pushOperation[w,p, p'] 
  // no pointer without push operation.
  Push.f.univ + Push.f[univ] = HeadPointer
}

ここまででインスタンスを出してみる.

f:id:osiire:20160408022626p:plain

当然のことながらスタックは壊れる。. スクリーンショットの下の方にあるStackElem3を見ると、他の2つのポインタからnext参照されており、
これは同時に2方向にスタックが伸びたことを意味する。並行動作に対して制約を入れないといけない。

ここでCASの出番。CASを使えばポインタを書き換える操作を逐次的にできる。HeadPointer -> HeadPointerという2項関係を直線(連結、分岐なし、合流なし、循環なし)にすればよい。これが意外と難しくて、何も考えずに書くとhigher orderでskolem化できないとAlloyに怒られる。試行錯誤した結果、下記の条件で二項関係を直列化できた。

pred linear[f:set elem -> elem] {
   let xs = f.univ + f[univ] | {
      // linear(no branch and merge)
      all x:xs | lone f.x && lone f[x]
      // connected and two edges.
      let s = f.univ & f[univ] | one (f.univ - s) && one (f[univ] -s )
      // non cyclic
      no ^f & iden      
   }
}

この制約をCASとして導入する。

fact useCASforRewritePointer {
   linear[Push.f]
}

これで完璧。下記のようなインスタンスが得られる。

f:id:osiire:20160408233419p:plain

linearに手間取って最初の構造で力尽きた感じ。