F#解决僵局

时间:2013-10-15 16:07:04

标签: concurrency f#

我对并发编程很新,所以我遇到了一个我需要解决的死锁问题。

因此,对于下面的代码,它不会打印任何我怀疑必须存在死锁的内容,尽管我不太确定它是如何发生的。

let sleepMaybe() = if (random 4) = 1 then Thread.Sleep 5

type account(name:string) =
    let balance = ref 1000
    member this.Balance = lock this <| fun () -> !balance
    member this.Name = name
    member this.Withdraw amount = balance := !balance - (sleepMaybe(); amount)
    member this.Deposit amount  = balance := !balance + (sleepMaybe(); amount)

    member this.Transfer (toAcc:account) amount = 
        lock this <| fun () ->  lock toAcc <| fun () -> toAcc.Deposit amount
                                this.Withdraw amount


let doTransfers (acc:account) (toAcc:account) () = 
    for i in 1..100 do acc.Transfer toAcc 100
    printfn "%s balance: %d  Other balance: %d" acc.Name acc.Balance toAcc.Balance

let q2main() = 
    let acc1=account("Account1") 
    let acc2=account("Account2")

    startThread (doTransfers acc1 acc2)
    startThread (doTransfers acc2 acc1)

q2main()         

3 个答案:

答案 0 :(得分:3)

您正在锁定实例本身并要求锁定两个实例以传输内容。这是死锁的秘诀。

  • 线程1锁定acc1以开始传输
  • 线程2锁定acc2以开始传输
  • 线程1等待释放acc2上的锁,以便它可以完成传输
  • 线程2等待acc1上的锁定被释放,以便它可以完成传输

他们每个人都会等待对方无限期地放开他们的锁定。

如果一次获取多个锁,总是以相同的顺序获取锁。也就是说,通过更改对象职责,尽量不要一次锁定多个锁。

例如,提款和存款是两个不相关的单独操作,但它们会修改余额。您正尝试使用锁保护余额。一旦帐户的余额发生变化,再也无法保持这种锁定。此外,我建议知道如何转移到其他帐户不是帐户的责任。

考虑到这一点,这里有一些消除死锁的变化。

type Account(name:string) =
    let mutable balance = 1000
    let accountSync = new Object()

    member x.Withdraw amount = lock accountSync 
                                  (fun () -> balance <- balance - amount)
    member x.Deposit amount =  lock accountSync 
                                  (fun () -> balance <- balance + amount)

let transfer amount (fromAccount:Account) (toAccount:Account) =
    fromAccount.Withdraw(amount)
    toAccount.Deposit(amount)

答案 1 :(得分:2)

克里斯解释了死锁的原因,但解决方案必须涉及锁定两个帐户的整个转移(假设存款可能因透支而失败等)。你正在有效地争取一种形式的交易记忆。这是一种方法:

open System
open System.Threading
open System.Threading.Tasks

type Account(name) =
  let mutable balance = 1000
  member val Name = name
  member __.Balance = balance
  member private __.Deposit amount =
    balance <- balance + amount
  member val private Lock = obj()
  member this.Transfer (toAccount: Account) amount =
    let rec loop() =
      let mutable retry = true
      if Monitor.TryEnter(this.Lock) then
        if Monitor.TryEnter(toAccount.Lock) then
          this.Deposit(-amount)
          toAccount.Deposit(amount)
          Monitor.Exit(toAccount.Lock)
          retry <- false
        Monitor.Exit(this.Lock)
      if retry then loop()
    loop()

let printLock = obj()

let doTransfers (acc:Account) (toAcc:Account) threadName = 
    for i in 1..100 do 
      acc.Transfer toAcc 100
      lock printLock (fun () ->
        printfn "%s - %s: %d, %s: %d" threadName acc.Name acc.Balance toAcc.Name toAcc.Balance)

[<EntryPoint>]
let main _ = 
    let acc1 = Account("Account1") 
    let acc2 = Account("Account2")
    Task.WaitAll [|
      Task.Factory.StartNew(fun () -> doTransfers acc1 acc2 "Thread 1")
      Task.Factory.StartNew(fun () -> doTransfers acc2 acc1 "Thread 2")
    |]
    printfn "\nDone."
    Console.Read()

答案 2 :(得分:0)

这是dining philosophers problem。一般的解决方案是在获取锁之前对其进行排序。