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争夺单个资源很简单,你只需等待它可用。争夺多个资源更为困难,因为如果它们被无序获取,您可能会死锁(正如您所经历的那样)。按顺序获取锁意味着您实际上在等待单个资源。