Article Options
Premium Sponsor
Premium Sponsor

 »  Home  »  .NET Framework  »  Multithreading in VB.NET  »  Synchronization
Multithreading in VB.NET
by John Spano | Published  07/16/2005 | .NET Framework | Rating:
Synchronization

Synchronization

    So what is thread synchronization?Imagine the following lines of code:

Dim X as Integer

X = 1

X = X + 1

    To a programmer the line X = X + 1 is a single operation.  But consider this line from a computer’s perspective.  Computers use machine language, which could mean many separate operations for each line of code.  For example the line above could be broken down in to several operations, such as:Move the value of X into a register, move the value 1 into another register, add the two registers and place the value into a third register and finally move the added values into the memory address of the variable X

    Imagine the above situation with multiple threads trying to access the variable X at the same time.  Synchronization is the process of eliminating these kinds of errors.  Without synchronization programming, the computer could stop the first thread at any point in time, and let the second access the variable.  If the second thread was incrementing X by 1 also, it might finish, and then the computer resumes the original thread that was running.  This thread would restore its variable information, replacing the new X with the old value, nullifying the work that the second thread accomplished.  This is called a race condition.  These errors are very hard to find.  It is best to put time in preventing them.  . 

    To synchronize code, you utilize locks.  A lock is a way to tell the computer that the following group of code should be executed together as a single operation, and not let other threads have access to the resource that is locked until the locking code is finished.  In the case study, we will examine the different types of locks and objects that allow locking, and discuss when to use each method.  When your code can handle multiple threads, safely, it is considered thread safe.  This common term is used on code libraries and controls to designate that they are compatible with multiple threads. 

    Synchronization also adds a new type of bug you have to watch out for, deadlocking.  Deadlocking can occur if you aren’t careful with your locking techniques.  For example, assume that we have two resources, A and B.  Thread 1 calls and locks resource A at the same time thread 2 calls and locks resource B.  Thread 1 then requests resource B and thread 2 requests resource A.  This is called a deadlock.  Thread 1 can’t release resource A until it gets resource B, and thread 2 can’t release resource B until it gets A.  Nothing happens and your system can’t ever complete either of the two threads.  Needless to say this is very bad. 

    The only way to avoid deadlocks is to never allow a situation that could create one.  Code both threads to allocate resources in the same order.  Have thread 1 allocate A and then B, and the same with thread 2.  This way thread 2 will never start until thread 1 is finished with resource A.  Then it will wait until thread 1 is finished with resource B before continuing, and avoid the deadlock.  Another good practice is to lock resources as late as possible.  Try to avoid getting locks until you absolutely need them and then release them as soon as possible.  Next we shall take a look at all the different methods of thread synchronization that the common language runtime provides. 

Interlocked Class

    Because it is a very common programming technique, variable increment and decrement have their own framework class, the Interlocked class.  This class provides simple thread safe methods to do some common tasks with variables.  The Increment and Decrement methods add or subtract 1 from a variable.  These methods can be considered “atomic”.  This means that the operating system will consider the entire operation as one not allowing other threads to interrupt their execution.  The class is a member of System.Threading.  To use the functions without fully qualifying the name, add an Imports System.Threading line.  I will assume that the System.  Threading namespace has been imported for all examples in the case study. 

 Dim X as Integer

X = 1

X = Interlocked.Increment(X)

X = Interlocked.Decrement(X)

    The above code ensures that the computer will not interrupt the increment or decrement of the variable X

    There are two additional methods in the Interlocked class, Exchange and CompareExchange.  Let’s take a closer look at the two.  The Exchange method replaces the value of a variable with the value supplied.  The second value could be a hard coded value or a variable.  Don’t let the name of the method, Exchange, confuse you though.  Only the first variable passed in the first parameter will be replaced by the second.  The method won’t really exchange the values of two variables. 

 Dim X as Integer = 5

Dim Y as Integer = 1

Interlocked.Exchange(X, Y)‘X now equals 1 Y is still 1

Interlocked.Exchange(X, 4) ‘X now equals 4

 CompareExchange will do a comparison of two variables, and if equal, replace the one used as the first parameterwith the supplied value. 

 Dim i As Integer

i = 200

Interlocked.CompareExchange(i, DateTime.  Now.  Day, 200)

    The above code creates a new integer and then assigns the value 200 to it.  We then call the Interlocked.CompareExchange.  The method compares the variable i with 200 and since they are the same, it will replace i with DateTime.  Now.  Day, the current day of the month. 

    The Interlocked class allows you to do basic programming techniques and make them thread safe.  Let’s examine how to do more than just basic commands now.  The Dot Net framework provides several classes, and Visual Basic.  Net provides one method to handle complete synchronization.  First we will take a look at the SyncLock Visual Basic.Net keyword. 

SyncLock Keyword

   The SyncLock keyword (lock in C#) gives an easy way to quickly lock parts of code.  It is a built in keyword in Visual Basic.  Net now.  Take a look at the following code segment:

Dim sText as String

Dim objLock as Object = New Object()

SyncLock objLock

sText = “Hello”

End SyncLock

    First we declare a new string, sText.  Then we set up a SyncLock block to control access to the object using another locking object, objLock.  This guarantees that only one thread at a time can set the object to the string “Hello”.  A lock object must be used or an exception will be thrown on the Exit call.  If you try to use an object that has changed since the Enter call, the Exit will fail, so you cannot lock on sText itself.  The most common use of SyncLock is to lock the entire object it is in by using the Me keyword as the parameter of the SyncLock.  This will lock the object for all threads except the executing one.  This provides a very high degree of control over the locking patterns of the object at the cost of flexibility. 

 Public Sub Foo()

Dim sText as String

SyncLock Me

sText= “Hello”

End SyncLock

End Sub

    Locking the entire object is usually a great waste of time and processing power.  Other methods in the Me object that have locking code based on the Me object won’t be accessible to any threads while in the lock.  If a more flexible approach is needed, a locking variable can be used.  Locks can also only be obtained on reference types.  If a lock on a value type is needed, you must use a locking object as shown below.  The code locks access to iData via a reference type, System.  Object.  Imagine the locking object as a key to the code.  Only one thread at a time can have the key.  This allows for much greater control over what gets locked.  This method will also not lock the whole Me object.  Other threads are free to access other methods of Me, which is much more efficient and will reduce the possibility of deadlocks. 

 Public Sub Foo()

Dim iData as Integer

Dim objLock as Object = New Object()

SyncLock objLock

iData = 3

End SyncLock

End Sub

    One drawback to using SyncLock is that other threads must wait forever for the lock to be released if they need the locked resource.  They will never time out.  If you aren’t careful and enter an infinite loop in the locking thread, or hog resources, you can easily create deadlocks or periods of time where nothing happens.  In later sections, better methods of synchronization will be discussed. 

    Flow control statements such as GoTo cannot move the code flow into a SyncLock block of code.  The thread must execute the SyncLock keyword.  Old Visual Basic 6 error handling cannot be used from inside a SyncLock block either since it uses exception handling internally.  Since all new code should be written with exception handling, you probably won’t run into a situation like this unless upgrading a legacy application.  I would highly recommend rewriting any legacy error handling even if the methods aren’t used for multithreading.  Neither of the following code blocks will compile:

 SyncLock Me

On Error GoTo Errhandle ‘won’t compile

Dim i As Integer

i = 5

End SyncLock

Exit Sub

Errhandle:

 Or:

 GoTo EnterHere‘won’t compile

SyncLock Me

EnterHere:

Dim i As Integer

i = 5

End SyncLock

    In the next section we will examine how SyncLock works internally. 

Monitor Class

    To examine how SyncLock works, we have to explore a framework class, the Monitor class.  The Monitor class does exactly what it says: monitors the access to a region of code and prevents multiple threads from entering.  If you are familiar with win32 programming using C++, Monitor is similar to a critical section.  Monitor creates a lock on an object that doesn’t allow any other threads to obtain access to the object until released by the locking thread.  These locks are on sections of memory, hence the common name critical section.  We will first see how to control access to a block of code, just like with the SyncLock keyword. 

    The Enter function of the Monitor class works just like the SyncLock keyword and the Exit function is like the End Synclock keywords.  Internally SyncLock uses the Monitor class to implement its functionality and generates the inner Try Finally block of the code sample for you.  Let’s look at the code now:

 Public Sub Foo()

Dim sText as String

Dim objLock as Object = New Object()

Try

Monitor.Enter(objLock)

Try

sText = “Hello”

Finally

Monitor.Exit(objLock)

End Try

Catch e as Exception

MessageBox.Show(e.Message)

End Try

End Sub

    This provides the exact same functionality that the SyncLock example above did.  You will also notice that the Exit is contained in the finally clause of a Try Catch Finally block.  This is to ensure that Exit gets called so the thread won’t get locked infinitely.  Monitor.  Enter is also called outside of the Try Catch Finally block.  This is so Monitor.  Exit won’t get called if the Enter method doesn’t, as it will throw another exception.  So why should we use Monitor, as the SyncLock keyword provides the same functionality without the extra work of Monitor.  We will examine the reasons why Monitor should be used as we look at the other methods of Monitor

    We said earlier that the SyncLock block would wait indefinitely on the executing thread to release the lock.  The Monitor class provides a much better method to handle this, the TryEnter method.  This is the first reason why you would use Monitor over SyncLock.  This method will allow the calling thread to wait a specific amount of time to acquire a lock before returning false and stopping its execution.  This allows graceful handling of long running threads or deadlocks.  If a deadlock has occurred, you certainly do not want to add more threads that are trying to get to the deadlocked resource. 

    The default method of no parameters, will try to acquire a lock, and if unsuccessful it will return false immediately.  There are also two additional overloads that will wait for the specific number of milliseconds, or the specified TimeSpan.  This offers much more flexibility than SyncLock

  Public Sub Foo()

Dim sText As String

Dim objLock As Object = New Object()

Dim bEnteredOk As Boolean

bEnteredOk = Monitor. TryEnter(objLock, 5000)

If bEnteredOk = True Then

sText = "Hello"

Monitor.Exit(objLock)

End If

End Sub

    This example will try to acquire a lock for five seconds.  If successful the stringis set to “Hello”. 

    The rest of Monitor’s methods must be examined together.  The SyncLock keyword and the Monitor.  Enter rely on putting waiting threads to sleep to stop their execution.  This isn’t the best practice to follow as there is no way to get them to stop waiting unless aborted.  The Monitor.  Wait and Monitor.  Pulse allow threads to wait on other conditions before starting.  The methods will place the thread in a wait state allowing other threads to specify when they need the waiting thread to run.  An example of this is a queue.  You could have a thread that waits in an idle state until other threads place objects in the queue for it to work on. 

    To use the methods you first tell a thread to wait on an object with a Monitor.  Wait call like below. 

Dim objLock As Object = New Object()

Dim bPulsed As Boolean

Monitor.Enter(objLock)

bPulsed = Monitor.Wait(objLock)

If bPulsed Then

'thread was pulsed

End If

Monitor.Exit(objLock)

    The thread is automatically unlocked with the Wait call.  You must be sure to call Monitor.  Exit when the thread is pulsed and done with its work, or you will have a block that could result in a deadlock.  The first thread will wait until the pulsing thread has released its lock.  This will make the thread wait until a Monitor.  Exit is called, like the following. 

 Monitor.Enter(objLock)

Monitor.Pulse(objLock)

Monitor.Exit(objLock)

    If the Exit call is left off a block occurs because the waiting thread cannot obtain its lock on the object that the pulsing thread has.  You must also use the same object to lock on, and pulse from the second thread that the waiting thread used to wait on, objLock.  Also, both Wait and Pulse must be called from a locked block of code, hence the Enter and Exit calls in the above code.  You should exit immediately after calling Pulse to allow the first thread to perform its work, since the pulsing code has the current lock on objLock

    The Monitor class also comes equipped with a PulseAll method.  Unlike Pulse, which will only start the next waiting thread, PulseAll removes the wait state from all waiting threads and allows them to continue processing.  As with the Pulse method, PulseAll must be called from a locked block of code, and on the same object that the original threads are waiting on. 

    The Monitor class will provide for most of your threading synchronization needs.  It should be used unless a more specific task calls for the next few classes we will examine.  Here is a review of some good practices to follow when using Monitor:

  1. Exit MUST be called the same number of times Enter is called, or a block will occur. 
  2. Make sure that the object used to call Enter is the same object that is used to call Exit or the lock will not be released. 
  3. Don’t call Exit before calling Enter, or call Exit more times than calling Enter or an exception will occur. 
  4. Place the Exit method call in a Finally block.  All code that you wish to lock should be in the Try section of the corresponding Finally block.  The Enter call should be in its own Try block.  This eliminates calling Exit if the Enter fails. 
  5. Don’t call Enter on an object that has been set to Nothing or an exception will occur. 
  6. Don’t change the object that you use as the locking object, which brings in 7,
  7. Use a separate locking object, and not the changing object.  If you use an object that has changed, an exception will be generated. 

 MethodImplAttribute

    Code attributes in the Dot Net Framework can sometimes make programming easier.  The MethodImplAttribute is one example of the hundreds of different attributes that you can use.  It is in the System.  Runtime.  CompilerServices namespace.  This attribute is particularly interesting to synchronization because it can synchronize an entire method with one simple command. 

    If you place the attribute before a function and supply the MethodImplOptions.  Synchronized enumeration in the constructor, the entire method will be synchronized when called.  The compiler will create output that wraps the whole function, MySyncMethod, in a Monitor.  Enter and Monitor.  Exit block.  When a thread starts to enter the method it will acquire a lock.  Upon exiting the method, it will release the lock.  Here is an example of using the attribute. 

 <METHODIMPLATTRIBUTE(METHODIMPLOPTIONS.SYNCHRONIZED)>Private

Sub MySyncMethod()

End Sub

    This attribute should only be used when an entire function needs to be synchronized, so it is rarely used.  If you can exit the synchronized block of code before the end of the method or wait to enter it to the middle of the method, Monitor should be used, as the attribute would waste processing cycles by locking the whole method and not just what needs to be synchronized. 

Sponsored Links