Concurrency F#解决死锁

Concurrency F#解决死锁,concurrency,f#,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 () -> !bala

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

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

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()         
让sleepMaybe()=if(random 4)=1然后线程。睡眠5
类型帐户(名称:字符串)=
让天平=参考1000
记住这个。平衡=锁定这个!平衡
成员:this.Name=Name
此成员。提取金额=余额:=!余额-(可能();金额)
此会员。存款金额=余额:=!余额+(可能();金额)
此成员。转账(toAcc:账户)金额=
将此锁锁定到存款金额
这是你的提款金额
转让(会计科目:账户)(会计科目:账户)()=
对于1..100中的i,根据转移到CC 100
打印fn“%s余额:%d其他余额:%d”科目名称科目余额到科目余额
让q2main()
设acc1=账户(“账户1”)
让acc2=账户(“账户2”)
startThread(点传输acc1 acc2)
startThread(点传输acc2 acc1)
q2main()

您正在锁定实例本身,并要求两个实例都被锁定以传输某些内容。这是造成僵局的秘诀

  • 线程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)
类型帐户(名称:字符串)=
设可变平衡=1000
让accountSync=新对象()
成员x.提取金额=锁定帐户同步

(fun()->balance balanceChris解释了死锁的原因,但解决方案必须包括在整个转账过程中锁定两个帐户(假设存款可能因透支等原因而失败)。您实际上是在努力寻找一种事务性内存。这里有一种方法:

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()
开放系统
开放系统。线程
开放系统.Threading.Tasks
类型帐户(名称)=
设可变平衡=1000
成员val Name=Name
成员余额=余额
会员私人存款金额=
余额点传输acc1 acc2“线程1”)
Task.Factory.StartNew(fun()->doTransfers acc2 acc1“Thread 2”)
|]
printfn“\n完成。”
Console.Read()

这是最新版本。一般的解决方案是在取锁之前对锁进行排序。

您需要发布实际代码。这段代码不起作用。例如,它不是随机的,它是随机的(F#区分大小写)。另外,据我所知,没有“startThread”,但有一个Thread.Start。从答案中可以看出,正确使用锁是一个相当大的挑战。作为一个长期滥用密码的人,我建议你学习如何正确使用密码锁,但尽可能避免使用密码锁。通过传递消息而不是共享状态,可以更轻松地解决大多数问题。看看F#中的MailboxProcessor。谢谢stmax,但我还不知道如何正确使用锁,所以我尽量避免在这里使用邮箱,因为我个人觉得异步更容易理解。谢谢Daniel,但是我尽量不使用轮询或邮箱来执行此任务:)我不明白为什么要使用新对象来执行锁定,而我们只需锁定类对象本身(即lock x)。我知道你的代码是有效的,但我不完全确定为什么。@user2431438这是一种预防措施。就功能而言,锁定哪个实例并不重要,只要它是影响给定对象的所有操作的同一实例(在本例中是平衡)。但是,锁定类实例本身允许任何可以看到该实例的人锁定它,这可能会影响帐户类型有效管理自身的内部能力(例如,有人在该实例上死锁,现在该帐户被冻结)。在类型中创建私有实例可确保只有帐户类型可以使用它。@user2431438:您不应该锁定外部可见的对象,因为库的用户也可以锁定外部可见的对象。因此,像Chris一样使用私有对象是一种很好的做法。感谢您的澄清,这非常清楚。想知道你说的这句话是什么意思“如果你必须一次获取多个锁,那么总是以相同的顺序获取锁”。@user2431438争夺单个资源很简单,你只需等待它可用。争夺多个资源更为困难,因为如果它们被无序获取,您可能会死锁(正如您所经历的那样)。按顺序获取锁意味着您实际上在等待单个资源。