tags:

views:

353

answers:

3
+5  Q: 

.NET 4 SpinLock

The following test code (F#) is not returning the result I'd expect:

let safeCount() =
  let n = 1000000
  let counter = ref 0
  let spinlock = ref <| SpinLock(false)
  let run i0 i1 () =
    for i=i0 to i1-1 do
      let locked = ref false
      try
        (!spinlock).Enter locked
        if !locked then
          counter := !counter + 1
      finally
        if !locked then
          (!spinlock).Exit()
  let thread = System.Threading.Thread(run 0 (n/2))
  thread.Start()
  run (n/2) n ()
  thread.Join()
  !counter

I'd expect the SpinLock to mutually exclude the counter and, therefore, for it to return counts of 1,000,000 but, instead, it returns smaller values as if no mutual exclusion is occurring.

Any ideas what's wrong?

+7  A: 

SpinLock is a value type. When you dereference your spinLock variable (!spinLock), the struct got copied, and the lock you enter/exit is now different.

Mitya
Can this be solved?
Jon Harrop
+3  A: 

I don't know if there is a more elegant way, but if you wrap SpinLock in a class it works (I tried using a static and immutable SpinLock to no avail)

type SpinLockClass() =
    let s = System.Threading.SpinLock(false)
    member x.Enter locked = s.Enter(locked)
    member x.Exit() = s.Exit()

let safeCount() =
  let n = 1000000
  let counter = ref 0
  let spinlock = SpinLockClass()
  let run i0 i1 () =
    for i=i0 to i1-1 do
      let locked = ref false
      try
        spinlock.Enter locked
        if !locked then
          counter := !counter + 1
      finally
        if !locked then
          spinlock.Exit()
  let thread = System.Threading.Thread(run 0 (n/2))
  thread.Start()
  run (n/2) n ()
  thread.Join()
  !counter

EDIT: Stephen Swensen has a way to directly access a ref style SpinLock below

Guvante
Thanks. Looks like fields in classes are not copied but everything else. If that's right then it also has implications for other applications like complex arithmetic, where you want to avoid copying structs (and C# is currently much faster than F#).
Jon Harrop
+8  A: 

The reason why the SpinLock struct is being copied is because ! is a function: structs are copied when passed as arguments to a function or returned from a function (or any other kind of assignment for that matter). However, if you access the contents of the ref cell directly, no copying takes place.

let safeCount() =
  let n = 1000000
  let counter = ref 0
  let spinlock = ref <| SpinLock(false)
  let run i0 i1 () =
    for i=i0 to i1-1 do
      let locked = ref false
      try
        spinlock.contents.Enter locked
        if !locked then
          counter := !counter + 1
      finally
        if !locked then
          spinlock.contents.Exit()
  let thread = System.Threading.Thread(run 0 (n/2))
  thread.Start()
  run (n/2) n ()
  thread.Join()
  !counter
Stephen Swensen