Published:
02 Jun 2008
|
Abstract
In this first part of the series, Abhishek describes the concepts of threading, different procedures to create threads, synchronization among multiple threads, uses of Sleep and Join methods, code locking using SyncLock, and Monitor with sample code in Visual Basic 2005. |
|
by Abhishek Kumar Singh
Feedback
|
Average Rating:
Views (Total / Last 10 Days):
51990/
66
|
|
|
Introduction |
In terms of kernel scheduling, a process is a heavy unit,
whereas a thread is the smallest and lightest unit. Each process reserves its
own memory space (i.e. address space) as well as resources. Thread does not
specifically own resources except registers and stack. Each thread has to be a
part of at least one process. All threads which are part of single process
share the same address space. Overall it means that if we divide a heavy process
into multiple threads to execute concurrently (so called multithreading), we
can get the result much faster. Thinking like this feels good, but the major
responsibility on programmers, which must be known, understood, and applied in
multithreaded programs is, thread synchronization, thread communication
methodology, safe resource sharing among threads etc. In the following article
you will see about these one by one with lots of Visual Basic sample code (with
output details) written for better understanding. I have used Visual Basic 2005
for the code examples in this article.
Important Terms to Know About the Threading Model
·
Thread- The smallest unit of program or
part of a process, running in the execution area. Several threads can be the
part of a single process.
·
Process- An instance of a program. Any
single process contains at least one thread.
·
Context switching- A methodology used by
the operating system to switch the processor among threads to perform parallel
execution by giving time slices to each of them. Context
switching between processes is generally slower than context switching between
threads.
·
Kernel- Core of operating system, which
performs thread instantiation and execution. It provides several system call
interface for programmers to deal with threads to customize the execution
approach.
·
Multitasking (with multi-core CPU)- A
method of operating system in which it shares CPU time among multiple tasks
(processes) by switching between them on the basis of some predefined process
scheduling algorithms. As we all know, in single CPU machine, in truth, no two
processes can run at the same point of time. Multitasking gives a impersonate
way to it. But now multi-core CPUs are available in the
market by which more than one process can run concurrently depending upon the
number of CPU cores. Even single process is divided into sub processes
and processed by different CPU cores. In this way multi-core CPUs give better
performance.
·
Multithreading- An execution model which
allows several threads to execute independently under the context of a single
process. Multiple threads can run simultaneously, so called concurrent
execution. Threads can share process resources. So it gives faster execution
model. In other words we can say that multithreading is "multitasking
applied on processes."
|
Different ways to create threads in .NET |
Using System.Threading namespace
Listing 1 – Create threads using System.Threading
Sub Main()
Dim oThread As New System.Threading.Thread(New System.Threading.ThreadStart( _
AddressOf MyThreadJob))
oThread.Start()
Console.Read()
End Sub
Public Sub MyThreadJob()
Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString() & _
" This is MyThreadJob")
End Sub
Public Sub MyThreadJob()
Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString())
End Sub
In this case the thread method can be either sub or
function.
Using System.Windows.Forms.Timer namespace
We can use Windows.Forms.Timer object to create threads in
each specified interval. Timer object accepts time interval value in
milliseconds. Warning: Windows.Forms.Timer object may or may not work in
applications which are not windows based. For example console application. Even
if you add a reference to this namespace and your code compiles successfully,
it may fail to create threads during execution. So we should avoid using
Windows.Forms.Timer in non-form based applications. Use System.Threading.Thread
instead.
Here is the sample code for a windows application to create threads
using System.Windows.Forms.Timer
Listing 2 – Creating thread using
System.Windows.Forms.Timer.
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
Dim oTimer As New System.Windows.Forms.Timer()
AddHandler oTimer.Tick, AddressOf MyThreadJob
oTimer.Interval = 5000
Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString() & "
Starting Thread")
oTimer.Start()
End Sub
Private Sub MyThreadJob(ByVal o As Object, ByVal e As EventArgs)
MsgBox(Now.ToString() & " " & Now.TimeOfDay.ToString() & " This is MyThreadJob")
End Sub
Notice the signature of the thread method. In case of
Windows.Forms.Timer the thread method must have Object and EventArgs types in
its signature.
Using System.Threading.Timer namespace
Listing 3 – Creating thread using
System.Threading.Timer
Sub Main()
Dim oTimer As New System.Threading.Timer(New System.Threading.TimerCallback( _
AddressOf MyThreadJob), Nothing, 0, 5000)
Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString() & _
" System.Threading.Timer configured")
Console.Read()
End Sub
Private Sub MyThreadJob(ByVal s As Object)
Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString() & _
" This is MyThreadJob")
End Sub
Notice the signature of the thread method. In case of
System.Threading.Timer the thread method must have parameter of type Object. It
is normally used to share state information between threads.
Using System.Timers.Timer
Listing 4 – Creating thread using
System.Timers.Timer
Sub Main()
Dim oTimer As New System.Timers.Timer(5000)
AddHandler oTimer.Elapsed, AddressOf MyThreadJob
oTimer.Enabled = True
Console.Read()
End Sub
Private Sub MyThreadJob(ByVal s As Object, ByVal e As System.Timers.ElapsedEventArgs)
Console.WriteLine(Now.ToString() & " " & Now.TimeOfDay.ToString() & _
" This is MyThreadJob")
End Sub
Again, Notice the signature of the thread method MyThreadJob(). It must have Object and Timers.ElapsedEventArgs
types in the signature.
Among four methodologies mentioned above, which we can use
to build multithreaded applications, I would recommend going for the first
procedure (for using System.Threading.Thread)
always, unless we don't have any specific need of using others. Why? You can
find differences among these in the real world through MSDN and forums.
At this moment you should know how to create threads in
application. Each thread runs independently. Now your next task should be to
control these independent threads in such a manner that they all can run as per
our wish.
|
Thread Synchronization (A must do thing) |
Let's think your application (process) is a project manager
and each thread of it as a project team member. You surely want your team
members to work together, not independently, right? For this you would want
them to instruct and manage based on guidelines, work approach, periodic status
reporting, team communication, proper resource utilization, conflict prevention,
etc. You would want to have total control over the work environment to get the
final target to accomplish. So here you need to have synchronization among team
members. In the same way, we need synchronization among threads.
Important constructs which we can use for
Synchronization
Some important objects which we can use for Synchronization
are given below. We will see uses of all these in later part of the article.
1.
To block current thread
·
Sleep() - This is used very commonly to
block thread for some specified time interval (in milliseconds)
·
Join() - To block current thread and
instantiate another thread. When new thread finishes, it will resume its
processing.
2.
To implement code locking mechanism among
threads
·
SyncLock - To lock some part of
code to be run by only one thread at a time. SyncLock
term is used in VB.Net. Its C# equivalent is Lock.
·
Monitor - Similar to mutex in the
functionality but different coding approach with extra implementation features.
·
Mutex - To lock some part of code to be
run by only one thread at a time with cross-process accessibility feature.
Mutex is the extension of windows kernel objects.
·
Semaphore - To lock some part of code to
be run by only one thread at a time with cross-process accessibility feature,
with additional feature to prevent deadlock in better way. Semaphore is the
extension of kernel objects.
·
Synchronization Context - To lock some
part of code to be run by only one thread at a time just by using class
attribute [Synchronization] and inheriting ContextBoundObject class.
·
ReaderWriteLock - To allow specific
number of threads to run particular set of code simultaneously with some access
privilege set among them.
3.
To implement event-driven thread synchronization
in the application
·
EventWaitHandle - A class which provides
some methods and properties to manage threads by sending/receiving event signal
among each other. This also uses windows kernel object internally to perform
the task. Two important class which we use are:
·
1. AutoResetEvent
·
2. ManualResetEvent
I will describe the implementation procedures of each one
right from here. It will continue in my next few articles as well.
|
Implementing Sleep() and Join() methods in Threading |
Create a console application and update the code in module
as given below.
Listing 5 – Implementing thread creation without
using Sleep and Join methods
Module Module1
Sub Main()
Console.WriteLine("{0} : {1} : Main thread started...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
Dim oThOneMethod As Threading.ThreadStart = _
New Threading.ThreadStart(AddressOf DoAction)
Dim oThTwoMethod As Threading.ThreadStart = _
New Threading.ThreadStart(AddressOf DoAction)
Dim oTh1 As Threading.Thread = New Threading.Thread(oThOneMethod)
Dim oTh2 As Threading.Thread = New Threading.Thread(oThTwoMethod)
oTh1.Start()
oTh2.Start()
Console.WriteLine("{0} : {1} : Main thread finishing...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
End Sub
Public Sub DoAction()
Console.WriteLine("{0} : {1} : To sleep now...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
Threading.Thread.Sleep(5000)
Console.WriteLine("{0} : {1} : is woke up...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
End Sub
End Module
** [Please note that you should use
CTRL+F5 key combination to run the application with code provided in this
article since I have not used Console.Read() at the end of Main(). CTRL+F5
will cause the console to wait for any key press at the end. This way you can
see the output in the console window. Otherwise you can add Console.Read() in
main().]
|
If you run this application (CTRL+F5), you will get output
in console window a given below.
Figure 1 – Console output of application
Does the result seem proper? No, because in general we don’t
want to stop the main thread before finishing generated threads. Here is what
we need to use Join() method which will wait till new
threads finish. Modify the Main()
method to use Join() as given below. I have only
added oTh1.Join() and oTh2.Join()
in the Main() method.
Listing 6 – Implementing thread creation using
Sleep and Join methods
Sub Main()
Console.WriteLine("{0} : {1} : Main thread started...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
Dim oThOneMethod As Threading.ThreadStart = _
New Threading.ThreadStart(AddressOf DoAction)
Dim oThTwoMethod As Threading.ThreadStart = _
New Threading.ThreadStart(AddressOf DoAction)
Dim oTh1 As Threading.Thread = New Threading.Thread(oThOneMethod)
Dim oTh2 As Threading.Thread = New Threading.Thread(oThTwoMethod)
oTh1.Start()
oTh2.Start()
oTh1.Join()
oTh2.Join()
Console.WriteLine("{0} : {1} : Main thread finishing...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
End Sub
Now run the application (CTRL+F5), you should see following
console output.
Figure 2 – Console output of the application
This time result is as we wanted. New threads are finished
before the main thread.
|
Implementing thread lock using SyncLock |
This time we will move delegate method DoAction
to a separate class for better understanding. We will modify the code inside DoAction to use lock features. Let’s add a class CThread in
the application and add the DoAction method in it. In this example to use
SyncLock in it what we are going to do is just to put all lines of code in
DoAction method to be placed between SyncLock Me and End SyncLock constructs.
The complement class code is given below.
Listing 7 – Implementing SyncLock (locking feature)
in thread delegate method
Public Class CThread
Public Sub DoAction()
SyncLock Me
Console.WriteLine("{0} : {1} : To sleep now...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
Threading.Thread.Sleep(5000)
Console.WriteLine("{0} : {1} : is woke up...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
End SyncLock
End Sub
End Class
So we also need minor modification in the Main(). We just need to create object of class CThread in Main() and set thread start delegate method DoAction with class object. The code of Main()
is given below.
Listing 8 – Code for Main method of the application
Sub Main()
Dim oCThread As New CThread()
Console.WriteLine("{0} : {1} : Main thread started...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
Dim oThOneMethod As Threading.ThreadStart = _
New Threading.ThreadStart(AddressOf oCThread.DoAction)
Dim oThTwoMethod As Threading.ThreadStart = _
New Threading.ThreadStart(AddressOf oCThread.DoAction)
Dim oTh1 As Threading.Thread = New Threading.Thread(oThOneMethod)
Dim oTh2 As Threading.Thread = New Threading.Thread(oThTwoMethod)
oTh1.Start()
oTh2.Start()
oTh1.Join()
oTh2.Join()
Console.WriteLine("{0} : {1} : Main thread finishing...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
End Sub
Run the application (CTRL+F5). You should see output window
like:
Figure 3 – Console output of the application with
SyncLock uses
If you look the output carefully you can see that even if
thread 3 has gone to sleep for 5 seconds, thread 4 could not enter the SyncLock
section. In fact thread 4 just waits at the beginning of SyncLock till previous
thread (# 3 here) leaves the SyncLock section. When thread 3 woke up and comes
out of SyncLock section, thread 4 resumes its execution. In the same way, if
there would have been more thread like # 5,6,7 etc, all would have gone into a
thread queue and processed one by one with the rule that only
one thread can enter into the SyncLock at a time.
|
Implementing thread lock using Monitor |
We have another useful class in .net- Monitor.
The Monitor class provides functions through which we can implement locking
mechanism in the threads. In general it works similar to the SyncLock but the
advantage is that we can do lock based error handling with Monitor. For this we
can use Enter() and Exit()
functions with our usual error handling Try…Catch…Finally
block. Let's modify the DoAction method to use Monitor as given in the class
below.
To understand its implementation, just modify the existing
DoAction method to use Monitor class as given below. No need to make any change
in Main() for now.
Listing 9 – Thread delegate method using Monitor
class
Public Class CThread
Public Sub DoAction()
Try
Threading.Monitor.Enter(Me)
Console.WriteLine("{0} : {1} : To sleep now...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
Threading.Thread.Sleep(5000)
Console.WriteLine("{0} : {1} : is woke up...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
Catch ex As Exception
Console.WriteLine("{0} : {1} : Error in monitor section,exiting monitor...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
' do some job like error logging in file or database..
Finally
Threading.Monitor.Exit(Me)
End Try
End Sub
End Class
We can code Monitor.Enter and Monitor.Exit methods anywhere
inside the Try…Catch…Finally block of the delegate method. In this way it gives
more flexibility to implement and handle complex logic inside lock area.
Run the application (CTRL+F5), you would see console window
as:
Figure 4 – Console output window of Monitor
implementation
As this result same as the SyncLock example we had done
before. To test how Monitor is useful with Try…Catch…Finally block, we will
modify the DoAction method to read a file (say c:\abc.txt)
which does not exist in the computer. The very first instance of the delegate method
will be the victim of FileNotFound error. In catch block we will create that
file so that next all other threads will not get that error. Here is complete
code of class containing DoAction method.
Listing 10 – Thread delegate method using Monitor
class and Try…Catch…Finally to handle runtime errors.
Public Class CThread
Public Sub DoAction()
Try
Threading.Monitor.Enter(Me)
Console.WriteLine("{0} : {1} : To sleep now...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
Threading.Thread.Sleep(5000)
IO.File.ReadAllText("c:\abc.txt")
Console.WriteLine("{0} : {1} : is woke up...", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
Catch ioErr As IO.FileNotFoundException
Console.WriteLine("{0} : {1} : Error- File Not Found.", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString())
' create the file for next all threads
IO.File.Create("c:\abc.txt")
Catch ex As Exception
Console.WriteLine("{0} : {1} : Error in monitor section : {2}", _
Threading.Thread.CurrentThread.ManagedThreadId, Now.ToString(), ex.ToString())
' do some job like error logging in file or database..
Finally
Threading.Monitor.Exit(Me)
GC.Collect()
End Try
End Sub
End Class
One more thing you may have noticed that I have used GC.Collect()
at the end in finally block. I used it to release any resources (a file in this
example) being used by thread, so that other thread would not get any file
access error.
Run the application (CTRL+F5), you would see the console
output window as:
Figure 5 – Console output of the application
showing error handling with Monitor uses.
You can see in the result that managed thread # 3 got a
FileNotFound error and next thread # 4 executed normally since thread # 3
created the missing file in the Catch block.
Few sample project of this article can be downloaded though
the download links given below.
|
Downloads and Summary |
[Download
LockExample]
[Download MonitorExample]
Summary
So, that's it for this article. I will cover other very
interesting and useful threading models like Mutex, Semaphore etc in the next part
of this article. I hope this article would be helpful in understanding
threading concepts and implementation. Your suggestions and comments are
welcome. Thank you for giving time on ASPAlliance.
|
|
|
User Comments
Title:
Thread
Name:
Pari
Date:
2012-10-16 4:02:22 AM
Comment:
very helpful
|
Title:
Enginer
Name:
Didier Fonseca
Date:
2010-11-10 4:46:13 PM
Comment:
Thanks,very good and easy to understand tutorial
|
Title:
dig deeper
Name:
rs
Date:
2008-06-09 10:47:16 AM
Comment:
nice introduction in synchronization. next thing that comes up comparing different solutions is rating them. how do they differ (e.g. in performance) and which is suitable for what kind of problem ...
|
Title:
thanks
Name:
Abhishek Singh
Date:
2008-06-06 6:50:45 AM
Comment:
thanks to all of you!
|
Title:
Vey Good Article
Name:
Babita
Date:
2008-06-05 3:23:22 AM
Comment:
Very useful article about thread.
|
Title:
Good one
Name:
Soumya
Date:
2008-06-03 2:52:33 AM
Comment:
Very helpful definitions and examples.
|
Title:
nice article
Name:
rupesh
Date:
2008-06-03 2:40:32 AM
Comment:
Very nice article about thread
|
|
Product Spotlight
|
|