Library tutorials & articles
Multithreading in VB.NET
ReaderWriterLock Object
Many times, you read data much more often than you write it. Traditional synchronization can be overkill in these situations as it would lock resources when threads are reading or writing to the resource. A more efficient way has been added to the framework to handle this. The ReaderWriterLock is a synchronization class that allows multiple threads to read a variable, but only one thread to write to it at a time.
When acquiring a lock, the write thread must also wait until all reader threads have unlocked the object before obtaining an exclusive write lock. All readers will then be blocked until the writer thread releases its lock. The power of the class comes from the fact that it will allow multiple reader locks to access the resource at the same time. We will look first at how to acquire reader locks on an object.
Dim lData As Long = 1
Dim objLock As ReaderWriterLock
Private Sub btnRun_Click(ByVal sender As System. Object, _
ByVal e As System. EventArgs) Handles btnRun.Click
Dim Thread1 As Thread
Dim Thread2 As Thread
objLock = New ReaderWriterLock()
Thread1 = New Thread(AddressOf Thread1Work)
Thread2 = New Thread(AddressOf Thread2Work)
Thread1.Start()
Thread2.Start()
End Sub
Private Sub Thread1Work()
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
Console.WriteLine(lData & " Thread 1")
Thread.Sleep(10)
objLock.ReleaseReaderLock()
Next
End Sub
Private Sub Thread2Work()
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
Console.WriteLine(lData & " Thread 2")
objLock.ReleaseReaderLock()
Next
End Sub
We create an instance of a ReaderWriterLock object called objLock. Then two threads are spawned, both of which do a quick loop that writes the value of lData to the console window ten times. The first thread also has a ten-millisecond sleep call. This allows us to see that the second thread continues to get a reader lock on objLock even though the first already has one. Note also that we have passed a millisecond time limit to the methods. You must pass a timeout value to AcquireReaderLock. If you wish to wait infinitely, use the constant Timeout. Infinite.
The output should be something similar to the following:
1 Thread 1 1 Thread 2 1 Thread 2 1 Thread 2 1 Thread 2 1 Thread 2 1 Thread 2 1 Thread 2 1 Thread 2 1 Thread 2 1 Thread 2 1 Thread 1 1 Thread 1 1 Thread 1 1 Thread 1 1 Thread 1 1 Thread 1 1 Thread 1 1 Thread 1 1 Thread 1
This shows that the second thread ran while the first had a ReaderLock on the lData integer.
If needed, there is also a method IsReaderLockHeld that will return true if the current thread already has a reader lock. This helps keep track of multiple locks by one thread. For each call to AcquireReaderLock a subsequent call to ReleaseReaderLock is required. If you do not call ReleaseReaderLock the same number of times, the reader lock is never fully released, never allowing a write to the resource. IsReaderLockHeld can be checked to see if a reader lock is already active on the thread, and if so not acquire another one.
Now let’s examine how to update the variable. A writer lock can be obtained by calling AcquireWriterLock. Once all reader locks have been released, the method will obtain an exclusive lock on the variable. When updating the variable, all reader threads will be locked out until ReleaseWriterLock is called. Let’s examine the code for this.
Dim lData As Long = 1
Dim objLock As ReaderWriterLock
Private Sub btnRun_Click(ByVal sender As System. Object, _
ByVal e As System.EventArgs) Handles btnRun.Click
Dim Thread1 As Thread
Dim Thread2 As Thread
Dim Thread3 As Thread
objLock = New ReaderWriterLock()
Thread1 = New Thread(AddressOf Thread1Work)
Thread2 = New Thread(AddressOf Thread2Work)
Thread3 = New Thread(AddressOf Thread3Work)
Thread1.Start()
Thread2.Start()
Thread3.Start()
End Sub
Private Sub Thread1Work()
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
Console.WriteLine(lData & " Thread 1")
Thread.Sleep(100)
objLock.ReleaseReaderLock()
Next
End Sub
Private Sub Thread2Work()
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
Console.WriteLine(lData & " Thread 2")
Thread.Sleep(100)
objLock.ReleaseReaderLock()
Next
End Sub
Private Sub Thread3Work()
objLock.AcquireWriterLock(Timeout.Infinite)
lData = 2
Console.WriteLine("Thread 3 updated lData")
objLock.ReleaseWriterLock()
End Sub
You will notice that we have added a new thread, Thread3 and a function for it to run. This new function acquires a writer lock on the object and then updates lData to 2. The first two threads, Thread1 and Thread2, are put to sleep for one hundred milliseconds to allow thread three to start. When examining the output from this code, you will see that thread three waits until threads one and two release their locks. This thread three updates the variable. Thread one and two must then wait on it. As with the reader lock, there is also a method called IsWriterLockHeld that will return true if the current thread has a writer lock. You should get output similar to below:
1 Thread 1 1 Thread 2 Thread 3 updated lData 2 Thread 2 2 Thread 1 2 Thread 2 2 Thread 1 2 Thread 2 2 Thread 1 2 Thread 2 2 Thread 1 2 Thread 2 2 Thread 1 2 Thread 2 2 Thread 1 2 Thread 2 2 Thread 1 2 Thread 2 2 Thread 1 2 Thread 2 2 Thread 1
Another useful method of the ReaderWriterLock class is the UpgradeToWriterLock method. This method allows a reader lock to become a writer lock to update the data. Sometimes it is useful to check the value of a data item to see if it should be updated. Acquiring a writer lock to check the variable is wasted time and processing power. By getting a reader lock first other reader threads are allowed to continue accessing the variable until you determine an update is needed. Once the update is needed, UpgradeToWriterLock is called locking the resource for update as soon as it can acquire the lock. Just like AcquireWriterLock, UpgradeToWriterLock must wait until all readers accessing the resource are done. Now let’s look at the code.
Dim lData As Long = 1
Dim objLock As ReaderWriterLock
Private Sub btnRun_Click(ByVal sender As System.Object, _
ByVal e As System. EventArgs) Handles btnRun.Click
Dim Thread1 As Thread
Dim Thread2 As Thread
objLock = New ReaderWriterLock()
Thread1 = New Thread(AddressOf Thread1Work)
Thread2 = New Thread(AddressOf Thread2Work)
Thread1.Start()
Thread2.Start()
End Sub
Private Sub Thread1Work()
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
If lData = i Then
objLock.UpgradeToWriterLock(Timeout.Infinite)
lData = i + 1
Console.WriteLine("lData is now " & lData)
End If
Thread.Sleep(20)
objLock.ReleaseReaderLock()
Next
End Sub
Private Sub Thread2Work()
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
Console.WriteLine(lData & " Thread 2")
Thread.Sleep(20)
objLock.ReleaseReaderLock()
Next
End Sub
In this example, we have changed thread one to examine the value of lData after acquiring a reader lock. If the value of lData is equal to the looping variable of i (which it always is in our example) then it tries to obtain a writer lock by calling UpgradeToWriterLock. Nothing special is required to release the writer lock once finished with it. The normal ReleaseReaderLock will release the upgraded writer lock, or calling DowngradeFromWriterLock can be used also which will be discussed next. The output should be something similar to the following:
lData is now 2 2 Thread 2 lData is now 3 3 Thread 2 lData is now 4 4 Thread 2 lData is now 5 5 Thread 2 lData is now 6 6 Thread 2 lData is now 7 7 Thread 2 lData is now 8 8 Thread 2 lData is now 9 9 Thread 2 lData is now 10 10 Thread 2 lData is now 11 11 Thread 2
Opposite of UpgradeToWriterLock we can also use DowngradeFromWriterLock. Like its name suggests the method will make a writer lock turn to a reader lock. To use the function, you must pass it a LockCookie. This cookie can be generated from UpgradeToWriterLock. Because of the LockCookie requirement, you may only use DowngradeFromWriterLock on the same thread that UpgradeToWriterLock is called.
One advantage of DowngradeFromWriterLock is that the call returns immediately and will not block the thread at all. This happens because it can only be called from a thread that has a writer lock on an object. This means that no other thread can have a lock; hence the method knows that it is the only thread active on the object. If read access is still required to the resource this method will eliminate the need to reacquire a read lock on the thread. If read access is not required anymore, simply use ReleaseReaderLock as shown above. Let’s examine some code now.
Dim lData As Long = 1
Dim objLock As ReaderWriterLock
Private Sub btnRun_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnRun.Click
Dim Thread1 As Thread
Dim Thread2 As Thread
objLock = New ReaderWriterLock()
Thread1 = New Thread(AddressOf Thread1Work)
Thread2 = New Thread(AddressOf Thread2Work)
Thread1.Start()
Thread2.Start()
End Sub
Private Sub Thread1Work()
Dim i As Integer
Dim objCookie As LockCookie
For i = 1 To 10
objLock.AcquireReaderLock(1000)
If lData = i Then
objCookie = objLock.UpgradeToWriterLock(Timeout. Infinite)
lData = i + 1
Console.WriteLine("lData is now " & lData)
objLock.DowngradeFromWriterLock(objCookie)
Console.WriteLine("Downgraded lock")
End If
Thread.Sleep(20)
objLock.ReleaseReaderLock()
Next
End Sub
Private Sub Thread2Work()
Dim i As Integer
For i = 1 To 10
objLock.AcquireReaderLock(1000)
Console.WriteLine(lData & " Thread 2")
Thread.Sleep(20)
objLock.ReleaseReaderLock()
Next
End Sub
The only differences in this code from the UpgradeToWriterLock are the lines:
objCookie = objLock.UpgradeToWriterLock(Timeout. Infinite)
And
objLock.DowngradeFromWriterLock(oCookie)
Console.WriteLine("Downgraded lock")
Instead of just waiting until the ReleaseReaderLock is called, we explicitly change the writer lock to a reader lock. The only real difference between downgrading and releasing the lock are with any other waiting writer locks. If you downgrade and still have waiting writer locks, they must continue to wait until the downgraded lock is released. You should see output similar to the following:
1 Thread 2 lData is now 2 Downgraded lock 2 Thread 2 lData is now 3 Downgraded lock 3 Thread 2 lData is now 4 Downgraded lock 4 Thread 2 lData is now 5 Downgraded lock 5 Thread 2 lData is now 6 Downgraded lock 6 Thread 2 6 Thread 2 lData is now 7 Downgraded lock 7 Thread 2 lData is now 8 Downgraded lock 8 Thread 2 lData is now 9 Downgraded lock 9 Thread 2 lData is now 10 Downgraded lock lData is now 11
Downgraded lock
Two other methods of note on the ReaderWriterLock class are ReleaseLock and RestoreLock. ReleaseLock immediately drops all locks that the current thread holds. It returns a LockCookie just like UpgradeToWriterLock that can be used in RestoreLock. When used, the LockCookie returns the thread back to the exact lock state that it held before. To handle the fact that other threads could have acquired locks on the object, the method will block until it can resolve all of its previous locks. The code is as follows:
Dim oLock As ReaderWriterLock
Private Sub btnRun_Click(ByVal sender As System.Object, ByVal _
e As System.EventArgs) Handles btnRun.Click
Dim Thread1 As Thread
Dim objCookie As LockCookie
objLock = New ReaderWriterLock()
Thread1 = New Thread(AddressOf Thread1Work)
objLock.AcquireWriterLock(Timeout.Infinite)
Thread1.Start()
Thread.Sleep(1000)
objCookie = objLock.ReleaseLock
Thread1 = New Thread(AddressOf Thread1Work)
Thread1.Start()
Thread.Sleep(1000)
objLock.RestoreLock(oCookie)
Thread.Sleep(1000)
Thread1 = New Thread(AddressOf Thread1Work)
Thread1.Start()
End Sub
Private Sub Thread1Work()
Try
objLock.AcquireReaderLock(10)
Console.WriteLine("Got a reader lock")
objLock.ReleaseReaderLock()
Catch
Console.WriteLine("Reader lock not held")
End Try
End Sub
Examining the code, we first see that a writer lock is acquired. Thread1 is then started to show that it can’t acquire a reader lock on the object. The main thread then releases the writer lock by calling ReleaseLock and saving its state to objCookie.Thread1 is then restarted acquiring the reader lock. A call to RestoreLock is called then with the LockCookie passed to it. When thread one is restarted at that point it cannot acquire its reader lock. The call to RestoreLock has replaced the writer lock on the object. The output looks like the following:
Reader lock not held Got a reader lock Reader lock not held
Another interesting pair of functions in the ReaderWriterLock class is the function WriterSeqNum and AnyWritersSince.WriterSeqNum returns the sequence number of the current lock in the internal queue of the ReaderWriterLock class. This queue keeps the order of the threads that have requested reader or writer locks on an object. AnyWritersSince will tell if any writer locks have been released since the call to WriterSeqNum. This is a good method to check if a piece of data has been updated on another thread. AnyWritersSince could be used in a large, time-consuming report situation. If no writers have updated the report data then there is no need to recalculate the report. The following code will show the methods in action.
Dim objLock As ReaderWriterLock
Private Sub btnRun_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnRun.Click
Dim objCookie As LockCookie
Dim SeqNum As Integer
Dim Thread1 As Thread
objLock = New ReaderWriterLock()
Thread1 = New Thread(AddressOf Thread1Work)
objLock.AcquireWriterLock(Timeout.Infinite)
SeqNum = objLock.WriterSeqNum
If objLock.AnyWritersSince(SeqNum) = False Then
Console.WriteLine("We see that no writers have released yet")
End If
objLock.ReleaseWriterLock()
Thread1.Start()
Thread1.Join()
If objLock.AnyWritersSince(SeqNum) = True Then
Console.WriteLine("We see that a writer has released now")
End If
End Sub
Public Sub Thread1Work()
objLock.AcquireWriterLock(Timeout.Infinite)
objLock.ReleaseWriterLock()
End Sub
First a writer lock is acquired on objLock. The sequence number is saved in SeqNum. Then a test to AnyWritersSince is made. Since no other threads have acquired any writer locks and released them, the method returns false. Next a thread, Thread1, is started and waited on. This thread simply acquires a writer lock and releases it. The main thread then checks AnyWritersSince again using the saved off sequence number. Since another thread has released a writer lock the method return true this time. The following output is returned.
We see that no writers have released yet We see that a writer has released now
Related articles
Related discussion
-
Sharepoint : GroupBy results according to custom property
by sampadasanjay (0 replies)
-
i have struck with my project in vb.net
by jetski (4 replies)
-
Error in VB code
by glib162002 (0 replies)
-
Very slow inserts using SqlCommand.ExecuteNonQuery()
by porchelvi (1 replies)
-
Datagridview Setting datasource property of datagridviewcomboboxcell at run time
by sairfan1 (2 replies)
Related podcasts
-
Concurrency Pt. 2
Podcast (MP3): Download Hosts: Alexander Michael Guests: Recording venue: In this second part of our concurrency series Michael and Alexander talk about basic patterns for concurrent programming, such as Active and Monitor Object, Scoped Locking and ...
Events coming up
-
Dec
6
Developing AJAX Web Applications with Castle Monorail
London, United Kingdom
Monorail is the model-view-controller engine of the Castle Project, bringing many of the best ideas of Ruby on Rails to the .NET world. In this talk, David De Florinier and Gojko Adzic show how Monorail makes it easy to develop .NET based AJAX applications, and how to use the Castle Project to build Web 2.0 applications effectively. Come to this session if you are a .NET web developer. Everyone is welcome!
Bundle of thanks to the author, it is very help full
As somewhat of an advanced "newbie" I have read many articles on Multithreading, and am at a loss
Do I need to have a delegate and call the delegate when I am using threading ?
Lets say I have a datatable with 5 million rows and I need to iterate through each row - grab some info , process that info (translate it) and then save it somewhere else. For example insert it inot a completely different database (and maybe even RDBMS - Architectur)
Now I currently for each row - but that takes a lot of time.
I would like to spawn a (Flexible Number of threads) to do the row processing - Will my For each move on as each thread runs. So I start Thread(1) will the next trigger, and I have a new row and can start thread(2) and so on. Also my Subroutine, must
Yes, but I decided not to go into detail since it's a vb.net article and you can't use it
Volatility means a lot more than is described in the article. It doesn't just affect the variable which has the volatile modifed; it affects the whole memory model for sections of code involving access to that variable.
I agree that there's no use of volatile which can't be semantically achieved using locks, and indeed I generally prefer to use locks myself. However, there is a speed difference between acquiring a lock and just using a volatile variable - in a very few cases it might be significant. I'm surprised VB.NET doesn't have any way of specifying this.
See the volatility page of my C# threading article for more information.
Jon Skeet
This thread is for discussions of Multithreading in VB.NET.