Introduction
This example show the problem that 2 thread are touching global variable without sync and solution to the problem.
This example is verry simple one so dont expect to any catches.
THe problem is starting when 2 thread touching global variable and make the operation of g_iNum++.
The problem is that this operation although written in one line , have 3 assembler operation.
1.mov g_iNum ax
2.inc ax
3.mov ax g_iNum
The problem is in case that the context switch between the threads is line number one.
Look at the following thread :
void NotSyncThread(int * a_iNum)
{
while(g_iGoOn)
{
(*a_iNum)++;
g_iNum++;
}
}
We are running this 2 threads of this kind and each thread will run N operation .
The expected result is that the global variable g_iNum will have the value of 2*N,
Because this is the number of operation that we are making on him ,
But as U can see from the example this is not like that and in the end we will get
g_iNum <= 2*N
More that I can say is that as long as the process is running g_iNum will be smaller than 2*N.
To solve this problem there are 3 optinal way.
1.First option is to use the function of the O.S InterLockedIncrement ( See function sync1)
this option is clear and there is no need to explain it .
void Sync1(int * a_iNum)
{
while(g_iGoOn)
{
(*a_iNum)++;
InterlockedIncrement((LPLONG)&g_iNum); //binary operation,
}
}
2.Second option is to use mutex,
This function is also well knows
void SyncWithMutex(int * a_iNum)
{
static HANDLE l_hmProtect=::CreateMutex(NULL,false,NULL);
if(l_hmProtect==NULL)
{
cout<<"There is problem in creating the The mutex\n";
return ;
}
while(g_iGoOn)
{
WaitForSingleObject(l_hmProtect,INFINITE); //starting the critical section,
//this is the syncronized area
(*a_iNum)++;
g_iNum++;
ReleaseMutex(l_hmProtect);
}
}
The reason that I am using static for the handle of the mutex is because that I want to create it only in case
that the user is calling this function .
3.Thirth way to solv the problem is using semaphore ,
void SyncWithSemaphore(int * a_iNum)
{
static HANDLE l_hsProtect=CreateSemaphore(NULL,1,1,NULL);
if(l_hsProtect==NULL)
{
cout<<"There is problem in creating the semaphore\n";
return ;
}
while(g_iGoOn)
{
WaitForSingleObject(l_hsProtect,INFINITE);
(*a_iNum)++;
g_iNum++;
ReleaseSemaphore(l_hsProtect,1,NULL);
}
}
The thirth way is to use semaphore , I will not get into details because this is the same principle as
the second thread.
As I said before this application is for beginer but I think that every programmer should encounter this problem
in real and not in thoery.
The use of (*a_iNum)++; inside the threas is to count how many operation this thread have done.
In the end of the process the sum of the number of the operation should be equal to the global number.
Because that I did not know how do I make link to download this program , I will put it here ,
Use copy past to visual studio.
Warning the program will not work in windows 98.
//start copy past from this line
#include<windows.h>
#include<iostream.h>
int g_iNum=0;
int g_iGoOn = 1;
//int g_iFirst = 0;
//int g_iSecond = 0;
void NotSyncThread(int * a_iNum)
{
while(g_iGoOn)
{
(*a_iNum)++;
g_iNum++;
}
}
void Sync1(int * a_iNum)
{
while(g_iGoOn)
{
(*a_iNum)++;
InterlockedIncrement((LPLONG)&g_iNum);
}
}
void SyncWithMutex(int * a_iNum)
{
static HANDLE l_hmProtect=::CreateMutex(NULL,false,NULL);
if(l_hmProtect==NULL)
{
cout<<"There is problem in creating the The mutex\n";
return ;
}
while(g_iGoOn)
{
WaitForSingleObject(l_hmProtect,INFINITE);
//this is the syncronized area
(*a_iNum)++;
g_iNum++;
ReleaseMutex(l_hmProtect);
}
}
void SyncWithSemaphore(int * a_iNum)
{
static HANDLE l_hsProtect=CreateSemaphore(NULL,1,1,NULL);
if(l_hsProtect==NULL)
{
cout<<"There is problem in creating the semaphore\n";
return ;
}
while(g_iGoOn)
{
WaitForSingleObject(l_hsProtect,INFINITE);
(*a_iNum)++;
g_iNum++;
ReleaseSemaphore(l_hsProtect,1,NULL);
}
}
void main()
{
typedef void (*TFunction)(int *); //typedef for pointer to function
TFunction func;
int l_iChoice;
cout<<"Choose 1, 2, 3, 4\n";
cout<<"1 for not sync option\n";
cout<<"2 for sync option with InterLocked Increment\n";
cout<<"3 for sync with Mutex\n";
cout<<"4 to sync with semaphore\n";
cin>>l_iChoice;
switch(l_iChoice)
{
case 1: func=NotSyncThread;
break;
case 2: func=Sync1;
break;
case 3: func=SyncWithMutex;
break;
case 4: func=SyncWithSemaphore;
break;
default:cout<<"The number is invalid exit the program\n";
return;
}
int l_iNum1=0;
int l_iNum2=0;
HANDLE l_htThread1 = ::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)func,&l_iNum1,0,0);
if(l_htThread1==NULL)
{
cout<<"There is problem in creating the thread\n";
return;
}
HANDLE l_htThread2=::CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)func,&l_iNum2,0,0);
if(l_htThread2==NULL)
{
cout<<"There is problem in creating the thread ( second thread ) \n";
return ;
}
::MessageBox(NULL,"PressOkToStopTheThreads","Press Ok",0);
g_iGoOn=0; //Stop the thread functionality.
cout<<"Number of iterations that run is "<<l_iNum1<<"+"<<l_iNum2<<"="<<l_iNum1+l_iNum2<<"\n";
cout<<"The value of the global number is "<<g_iNum<<"\n";
::CloseHandle(l_htThread1);
::CloseHandle(l_htThread2);
}
//end the copy past marking.
Of course that there are alot of other protocols that can solv this thing, The most thing that is important to me when
I show this is that the user will expirience this problem in reality and not in theory.