Взаимная блокировка
В процессе синхронизации блокировка устанавливается для объектов, а не потоков, поэтому при использовании разных объектов для блокировки разных фрагментов кода в программах иногда возникают весьма нетривиальные ошибки. К сожалению, во многих случаях синхронизация по одному объекту просто недопустима, поскольку она приведет к слишком частой блокировке потоков.
Рассмотрим ситуацию взаимной блокировки (deadlock) в простейшем виде. Представьте себе двух программистов за обеденным столом. К сожалению, на двоих у них только один нож и одна вилка. Если предположить, что для еды нужны и нож и вилка, возможны две ситуации:
- Один программист успевает схватить нож с вилкой и принимается за еду. Насытившись, он откладывает обеденный прибор, и тогда их может взять другой программист.
- Один программист забирает нож, а другой – вилку. Ни один не сможет начать еду, если другой не отдаст свой прибор.
В многопоточной программе подобная ситуация называется взаимной блокировкой. Два метода синхронизируются по разным объектам. Поток А захватывает объект 1 и входит во фрагмент программы, защищенный этим объектом. К сожалению, для работы ему необходим доступ к коду, защищенному другим блоком Sync Lock с другим объектом синхронизации. Но прежде, чем он успевает войти во фрагмент, синхронизируемый другим объектом, в него входит поток В и захватывает этот объект. Теперь поток А не может войти во второй фрагмент, поток В не может войти в первый фрагмент, и оба потока обречены на бесконечное ожидание. Ни один поток не может продолжить работу, поскольку необходимый для этого объект так и не будет освобожден.
Примечание
Диагностика взаимных блокировок затрудняется тем, что они могут возникать в относительно редких случаях. Все зависит от того, в каком порядке планировщик выделит им процессорное время. Вполне возможно, что в большинстве случаев объекты синхронизации будут захватываться в порядке, не приводящем к взаимной блокировке.
Ниже приведена реализация только что описанной ситуации взаимной блокировки. После краткого обсуждения наиболее принципиальных моментов мы покажем, как опознать ситуацию взаимной блокировки в окне потоков:
1 Option Strict On 2 Imports System.Threading 3 Module Modulel 4 Sub Main() 5 Dim Tom As New Programmer("Tom") 6 Dim Bob As New Programmer("Bob") 7 Dim aThreadStart As New ThreadStart(AddressOf Tom.Eat) 8 Dim aThread As New Thread(aThreadStart) 9 aThread.Name= "Tom" 10 Dim bThreadStart As New ThreadStarttAddressOf Bob.Eat) 11 Dim bThread As New Thread(bThreadStart) 12 bThread.Name = "Bob" 13 aThread.Start() 14 bThread.Start() 15 End Sub 16 End Module 17 Public Class Fork 18 Private Shared mForkAvaiTable As Boolean = True 19 Private Shared mOwner As String = "Nobody" 20 Private Readonly Property OwnsUtensil() As String 21 Get 22 Return mOwner 23 End Get 24 End Property 25 Public Sub GrabForktByVal a As Programmer) 26 Console.Writel_ine(Thread.CurrentThread.Name &_ "trying to grab the fork.") 27 Console.WriteLine(Me.OwnsUtensil & "has the fork.").. 28 Monitor.Enter(Me) 'SyncLock (aFork)' 29 If mForkAvailable Then 30 a.HasFork = True 31 mOwner = a.MyName 32 mForkAvailable = False 33 Console.WriteLine(a.MyName&"just got the fork.waiting") 34 Try Thread.Sleep(100) Catch e As Exception Console.WriteLine (e.StackTrace) End Try 35 End If 36 Monitor.Exit(Me) End SyncLock 37 End Sub 38 End Class