C++ C++ C# C# ASP.NET Security ASP.NET Security ASM ASM Скачать Скачать Поиск Поиск Хостинг Хостинг  
  Программа для работы с LPT портом...
Язык: .NET — ©Alexey...
  "ASP.NET Atlas" – AJAX в исполнении Micro...
Язык: .NET — ©legigor@mail.ru...
  "Невытесняющая" Многопоточность...
Язык: C/C++ — ©...
  Update World C++: Сборник GPL QT исходников
  Весь сайт целиком можно загрузить по ссылкам из раздела Скачать
Дебетовая карта Home Credit [CPS] RU

 Using events for thread synchronization / Threads / C#

Introduction

Whenever you have multiple threads in your program, which is just about always, and whenever those multiple threads might have to access the same resource, which is also a very probable contingency, you will need to incorporate some kind of thread synchronization technique. There are threads that will only access a resource in a read-only manner. Let's call them ReadThreads and then there are threads that will write to a resource. We call them WriteThreads or at least let's call them that for now. If a thread reads and writes a shared resource it will still be a WriteThread. The sample application is a windows forms application created using C# and which has three buttons, one for each case. You need to try out each button to see what happens in each of the cases we discuss below.

Case 1 - No synchronization

Alright let's imagine a situation where we have two ReadThreads that run in parallel and also access a shared object. In addition there is also a WriteThread that starts before the ReadThreads and which sets a valid value for the shared object. I have used Thread.Sleep to simulate processing time in the sample code snippets below.

Thread t0 = new Thread(new ThreadStart(WriteThread));
Thread t1 = new Thread(new ThreadStart(ReadThread10));
Thread t2 = new Thread(new ThreadStart(ReadThread20));
t0.IsBackground=true;
t1.IsBackground=true;
t2.IsBackground=true;
t0.Start();
t1.Start();
t2.Start();

As you can see, we have started the WriteThread and then immediately started our two ReadThreads. I'll show the code snippets for these functions below:-

public void WriteThread()
{
	Thread.Sleep(1000);
	m_x=3;
}	
public void ReadThread10()
{
	int a = 10;
	for(int y=0;y<5;y++)
	{
		string s = "ReadThread10";
		s = s + " # multiplier= ";
		s = s + Convert.ToString(a) + " # ";
		s = s + a * m_x;
		listBox1.Items.Add(s);
		Thread.Sleep(1000);
	}
}
public void ReadThread20()
{
	int a = 20;
	for(int y=0;y<5;y++)
	{
		string s = "ReadThread20";
		s = s + " # multiplier= ";
		s = s + Convert.ToString(a) + " # ";
		s = s + a * m_x;
		listBox1.Items.Add(s);
		Thread.Sleep(1000);
	}
}

When we run the program, we get the output shown below :-

Aha! So we have got the first two values wrong, one from each thread. What happened was that the ReadThreads started executing before the WriteThread had finished it's job. This is a totally unwanted situation and we should surely try and do something to avoid this.

Case 2 - Synchronization [One WriteThread - Many ReadThreads]

Now we are going to solve the problem we faced in Case 1. We'll use the ManualResetEvent thread synchronization class. As before we start the WriteThread and the two ReadThreads. The only difference is that we use safe versions of these threads.

Thread t0 = new Thread(new ThreadStart(SafeWriteThread));
Thread t1 = new Thread(new ThreadStart(SafeReadThread10));
Thread t2 = new Thread(new ThreadStart(SafeReadThread20));
t0.IsBackground=true;
t1.IsBackground=true;
t2.IsBackground=true;
t0.Start();
t1.Start();
t2.Start();

We also add a ManualResetEvent object to our class.

public ManualResetEvent m_mre;

We initialize it in our class's constructor.

m_mre = new ManualResetEvent(false);

Now let's look at out SafeWriteThread function

public void SafeWriteThread()
{
	m_mre.Reset();
	WriteThread();
	m_mre.Set();
}

The Reset function sets the state of the event object to non-signaled. This means the event is currently not set. Then we call the original WriteThread function. Actually we could have skipped the Reset step because we had set the state to non-signaled in the ManualResetEvent constructor earlier. Once the WriteThread function returns we call the Set function which will set the state of the event object to signaled. Now the event is said to be set.

Now, let's take a look at out two SafeReadThread functions.

public void SafeReadThread10()
{
	m_mre.WaitOne();
	ReadThread10();
}
public void SafeReadThread20()
{
	m_mre.WaitOne();
	ReadThread20();
}

The WaitOne function will block till the event object's signaled state is set. Thus in our particular scenario, both the SafeReadThreads will block till the event object is signaled. Our SafeWriteThread will set the event only after it has done it's job. Thus we ensure that the reading threads start reading the shared resource only after the writing thread has done it's job. Now when we run the program we get this output which is what we wanted to get.

Voila! Perfecto!

Case 3 - Synchronization [Many WriteThreads - Many ReadThreads]

Now assume we have a situation where we have two WriteThreads. Now the ReadThreads would have to wait till all the WriteThreads have finished their work. In a real scenario, both the WriteThreads would probably be running together, but in our example I've run them in a serialized order where the the second WriteThread starts only after the first one has finished. This is only for ease of simulation. In our case since the second WriteThread starts only after the first WriteThread the ReadThreads need to only wait on the second thread, but as I already said, simply imagine that the two WriteThreads are running in parallel.

We add another ManualResetEvent object for the second thread and also an array of ManualResetEvent objects.

public ManualResetEvent m_mreB;
public ManualResetEvent[] m_mre_array;

Now we add the following initialization code in our constructor

m_mreB = new ManualResetEvent(false);
m_mre_array = new ManualResetEvent[2];
m_mre_array[0]=m_mre;
m_mre_array[1]=m_mreB;

Now lets see how we start the four threads

Thread t0 = new Thread(new ThreadStart(SafeWriteThread));
Thread t0B = new Thread(new ThreadStart(SafeWriteThreadB));
Thread t1 = new Thread(new ThreadStart(SafeReadThread10B));
Thread t2 = new Thread(new ThreadStart(SafeReadThread20B));
t0.IsBackground=true;
t0B.IsBackground=true;
t1.IsBackground=true;
t2.IsBackground=true;
t0.Start();
t0B.Start();
t1.Start();
t2.Start();

As you can see, we now have two StartThreads and two WriteThreads. Lets see their implementations.

public void SafeWriteThread()
{
	m_mre.Reset();
	WriteThread();
	m_mre.Set();
}

As you can see, SafeWriteThread is same as before.

public void SafeWriteThreadB()
{	
	m_mreB.Reset();
	m_mre.WaitOne();
	Thread.Sleep(1000);
	m_x+=3;			
	m_mreB.Set();
}

Well, as you can see we have used another event object for this second WriteThread. For the sake of simulation there is a wait for the first thread to finish its work, but as mentioned before this is not a true representation of the real life state of affairs.

public void SafeReadThread10B()
{
	WaitHandle.WaitAll(m_mre_array);
	ReadThread10();
}

public void SafeReadThread20B()
{
	WaitHandle.WaitAll(m_mre_array);
	ReadThread20();
}

As you can see we have used a function called WaitAll. It's a static member function of the WaitHandle class which is the base class for our ManualResetEvent class. The function takes in an array of WaitHandle objects to which we pass our ManualResetEvent object array. The casting is implicitly done as we are casting to a parent class. What WaitHandle does is this. It will block till each object in the array has been put into a signaled state or in other words till every object in the array has been set. When we run the program this is what we get.

Cool, huh? It worked nice and fine.

AutoResetEvent

There is a very similar class called AutoResetEvent. The difference from the ManualResetEvent class is that the AutoResetEvent is automatically reset to non-signaled after any waiting thread has been released. The best I could figure out for the purpose of this class is this. Lets assume we have several threads waiting for access to an object. We don't want all of them to get access together. So when we are ready to allow access to one thread, we set the event object they are all waiting on. This object will be an AutoResetEvent object. Now one of the threads is released, but the moment that happens, the event is non-signaled automatically. Thus the other threads will continue to wait till the main thread or the thread that is accessing the event object decides to set the event to a signaled state.

I have put together a simple console application to demonstrate this class.

class Class1
{
	AutoResetEvent m_are;
	static void Main(string[] args)
	{
		Class1 class1 = new Class1();			

	}

	Class1()
	{
		m_are = new AutoResetEvent (false);
		Thread t1 = new Thread(new ThreadStart(abc));
		Thread t2 = new Thread(new ThreadStart(xyz));
		t1.Start();
		t2.Start();			
		m_are.Set();
		Thread.Sleep(3000);
		m_are.Set();
	}

	void abc()
	{
		m_are.WaitOne();
		for(int i=0;i<5;i++)
		{
			Thread.Sleep(1000);
			Console.WriteLine("abc abc abc");
		}
	}

	void xyz()
	{
		m_are.WaitOne();
		for(int i=0;i<5;i++)
		{
			Thread.Sleep(1000);
			Console.WriteLine("xyz xyz xyz");
		}
	}
}

When we run the above program we get something like this as output.

abc abc abc
abc abc abc
abc abc abc
xyz xyz xyz
abc abc abc
abc abc abc
xyz xyz xyz
xyz xyz xyz
xyz xyz xyz
xyz xyz xyz

Conclusion

This essay is not a comprehensive one in the sense it does not detail each and every little nuance associated with the thread synchronization event classes. But I do hope it gives you a start from where you can reach out to taller heights or perhaps the expression should be reach down to even more profound depths.




Дебетовая карта Home Credit [CPS] RU