Protecting
Access to Code with Critical Sections
Critical sections are one method of ensuring only a single thread
executes a region of code. They are declared within a process and are not
resources provided by the kernel (they have no handles). Since they are
entirely within the process, access to them is quicker than it would be if
access had to be brokered by the kernel.
The code in Listing 6.14 declares a critical section structure,
initializes it with a call to InitializeCriticalSection(), and, once the program has finished with it, deletes it
with a call to DeleteCriticalSection().
Listing 6.14 Using
Initialization and Deletion of Critical Sections
CRITICAL_SECTION
critical;
...
InitializeCriticalSection(&critical);
…
DeleteCriticalSection(&critical);
...
When a thread wants to enter the critical section,
it calls EnterCriticalSection(). If no other thread is in the critical section, the calling thread
acquires it and continues execution. If another thread is in the critical
section, the calling thread will sleep until the thread executing the critical
section leaves it with the call LeaveCriticalSection(). The thread that calls EnterCriticalSection() will not return until it has obtained access to the critical section;
there is no concept of a timeout.
Listing 6.15 shows an example of using a critical
section to protect access to the vari-able counter.
Listing 6.15 Using
a Critical Section to Protect Access to a Variable
volatile
int counter = 0;
CRITICAL_SECTION critical;
unsigned
int __stdcall test( void * )
{
while ( counter<100 )
{
EnterCriticalSection(
&critical );
int number = counter++;
LeaveCriticalSection(
&critical );
printf( "ThreadID %i; value =
%i, is prime = %i\n", GetCurrentThreadId(), number, isprime(number) );
}
return 0;
}
int
_tmain( int argc, _TCHAR* argv[] )
{
HANDLE h1, h2;
InitializeCriticalSection(
&critical );
h1 = (HANDLE)_beginthreadex( 0, 0, &test,
(void*)0, 0, 0); h2 = (HANDLE)_beginthreadex( 0, 0, &test, (void*)1, 0, 0);
WaitForSingleObject( h1, INFINITE );
WaitForSingleObject( h2, INFINITE );
CloseHandle( h1 );
CloseHandle( h2 ); getchar();
DeleteCriticalSection(
&critical );
return 0;
}
Putting threads to sleep and waking them up again is time-consuming,
because it involves entering the kernel. All critical sections should be
designed to be as short-lived as possible. With that in mind, it is likely that
by the time the thread has been put to sleep, the thread that was in the
critical section will already have left it. Therefore, mak-ing the waiting
thread sleep and then waking it up again is just a waste of time.
There are two alternatives. The programmer can call TryEnterCriticalSection(), which will return immediately returning either true, meaning that the
thread has acquired access to the critical section, or false, meaning that
another thread is currently in the critical section. The code that protects
access to the counter variable
could be written using TryEnterCriticalSection(), as shown in Listing 6.16.
Listing 6.16 Using TryEnterCriticalSection() to Avoid Putting Calling Threads to Sleep
while (counter<100)
{
while ( !TryEnterCriticalSection(
&critical ) ) {} int number = counter++;
LeaveCriticalSection(
&critical );
printf( "ThreadID %i; value = %i, is prime = %i\n",
GetCurrentThreadId(), number, isprime(number) );
}
This would cause the process to spin continuously
until it got the lock. One of the problems with having a thread spin is that it
is potentially depriving other threads of processor time. Of particular concern
would be the case where the spinning thread stops the other thread, which is
currently in the critical section, from getting back onto the processor.
Consequently, this style of programming is one that should only be under-taken
with care.
The other approach is to have the thread wanting to
enter the critical section spin briefly in the hope that the thread currently
in the critical section will soon leave. If the other thread leaves the
critical section, the spinning thread can immediately enter the critical
section. Once the thread has spun for a predetermined count, the thread goes to
sleep until the other thread eventually leaves the critical section. This
approach represents a trade-off between the immediacy of spinning for access to
the critical section and the poor utilization of resources that spinning
causes.
Critical sections support this idea of spinning for
a short time before sleeping. There are two ways of setting the number of times
that a thread calling EnterCriticalSection() will spin before it goes to sleep. The critical section can be
initialized with the value through the
initialization call
InitializeCriticalSectionAndSpinCount(), which takes the pointer to the critical section, and the spin count as parameters. Or, once the
critical section has been created, the spin count can be set through a call to SetCriticalSectionSpinCount(). Listing 6.17 shows calls to these routines.
Listing 6.17 Methods
of Setting the Spin Count for a Critical Section
InitializeCriticalSectionAndSpinCount( &critical, 1000 );
SetCriticalSectionSpinCount(
&critical, 1000 );
Related Topics
Privacy Policy, Terms and Conditions, DMCA Policy and Compliant
Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.