2

我对并发编程很陌生,所以我有一个需要解决的死锁小问题。

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

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()         
4

3 回答 3

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)
于 2013-10-15T17:57:47.747 回答
2

Chris 解释了僵局的原因,但解决方案必须涉及在整个转账过程中锁定两个账户(假设存款可能由于透支等原因而失败)。您正在有效地争取一种事务记忆形式。这是一种方法:

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()
于 2013-10-15T19:35:05.763 回答
0

这就是哲学家进餐的问题。一般的解决方案是在拿锁之前对它们进行分类。

于 2013-10-19T01:33:41.723 回答