Creating Native Windows Threads
A basic Windows application will start with a single thread. The
function call to request that Windows create a child thread is CreateThread(). This call takes the parameters shown in Table 6.1.
Table 6.1 Parameters
Passed to CreateThread()
The return value from the function call is a handle
for the created thread, which is a different construct than the thread ID.
Handles will be discussed in more detail later. A return value of zero means
that the call was unsuccessful.
All of the parameters, with the exception of the
address of the function to execute, will take sensible defaults if they are
provided the null value. Listing 6.1 shows code to create a child thread using
the CreateThread() call.
The call to GetCurrentThreadId() will
return an integer ID for the calling thread.
Listing 6.1 Creating
a Thread Using a Call to CreateThread()
#include <Windows.h>
DWORD WINAPI mythread(__in LPVOID
lpParameter)
{
printf( "Thread %i \n", GetCurrentThreadId()
); return 0;
}
int _tmain( int argc, _TCHAR* argv[] )
{
HANDLE handle;
handle =
CreateThread( 0, 0, mythread, 0, 0, 0 );
getchar(); return 0;
}
If it is important to capture the ID of the created
thread, the code shown in Listing 6.2 could be used. The thread ID is not very
useful, because most functions take the thread handle as a parameter.
Listing 6.2 Capturing
the ID of the Created Thread
int _tmain( int argc, _TCHAR* argv[] )
{
HANDLE handle;
DWORD
threadid;
handle = CreateThread( 0, 0, mythread, 0, 0, &threadid ); printf( "Thread %i \n", threadid );
getchar(); return 0;
}
Calling CreateThread() tells
the operating system to produce a new thread but does not set that thread up to
work with the libraries provided by the developer envi-ronment. Windows
essentially creates the thread and returns a handle to that thread.
However, the runtime libraries have not had the opportunity to set up
the thread-local data structures that they need. In most instances, the
libraries will create any structures that they need the first time that they
are called, but not all library calls are able to do that. Therefore, it is
recommended that instead of calling CreateThread(), the calls pro-vided by the runtime libraries are used. The two
recommended ways of creating a thread are the calls _beginthread() and _beginthreadex(). The two
functions take different parameters. Table 6.2 provides the parameters for _beginthread().
Table 6.2 Parameters
Passed to _beginthread()
Parameter Type :Comment
void(*)(void*) : The address of the function that the thread
will execute
unsigned
int: The stack size for the thread
void* :The pointer to the parameters that are to be
passed to the thread
Table 6.3 shows the parameters for _beginthreadex(). These are the same as the parameters taken by CreateThread().
Table 6.3 Parameters
Passed to _beginthreadex()
There is another difference between these two
routines other than the parameters that they take. A thread created by a call
to _beginthread() will
close the handle to the thread when the thread exits. The handle returned by _beginthreadex() will have to be explicitly closed by the programmer by calling CloseHandle(). This requirement is similar to the concept of detached threads in
POSIX.
The two functions also differ by the type of
function that the thread will execute. _beginthread()
is a void function and uses the default calling convention
__cdecl, whereas _beginthreadex() returns
an unsigned int and uses the __stdcall calling convention.
Both the _beginthread() and _beginthreadex()
functions return handles to the newly created threads. However, the actual
return type of the function call is uintptr_t, which has to be type cast to a HANDLE before it can be used in function calls that expect an object handle.
Listing 6.3 provides example code for creating threads using the three
different approaches discussed. The call to WaitForSingleObject() waits for an object to signal its readiness; in this instance, the
routine is passed the handle to a thread and waits for that thread to
terminate.
Listing 6.3 Three Different Ways of Creating Threads
#include <windows.h> #include
<process.h>
DWORD
WINAPI mywork1( __in LPVOID
lpParameter )
{
printf( "CreateThread thread %i\n", GetCurrentThreadId() );
return 0;
}
unsigned int
__stdcall mywork2( void * data )
{
printf( "_beginthreadex thread %i\n", GetCurrentThreadId() );
return 0;
}
void mywork3(
void * data )
{
printf( "_beginthread thread %i\n", GetCurrentThreadId() );
}
int _tmain( int argc, _TCHAR* argv[] )
{
HANDLE h1, h2, h3;
h1 =
CreateThread( 0, 0, mywork1, 0, 0, 0 );
h2 =
(HANDLE)_beginthreadex( 0, 0, &mywork2, 0, 0, 0 );
WaitForSingleObject( h2, INFINITE );
CloseHandle( h2 );
h3 =
(HANDLE)_beginthread( &mywork3, 0, 0 );
getchar();
}
Although calling _beginthread() looks
appealing because it takes fewer parameters and cleans up the handle after the
thread exits, it is better to use _beginthreadex().
The call to _beginthreadex() avoids a
difficulty with _beginthread(). If the
thread terminates, the handle returned by the call to _beginthread() will be invalid or even reused, so it is impossible to query the status
of the thread or even be confident that the handle to the thread is a handle to
the same thread to which it originally pointed.
Listing 6.4 shows an example of this problem.
Listing 6.4 Code
Where a Thread Handle Could Be Reused
#include <windows.h> #include
<process.h>
void
mywork1( void * data )
{
}
void
mywork2( void * data )
{
volatile int i;
for (i=0; i<100000; i++)
{} // because i
is volatile most compilers will not // eliminate the loop
}
int
_tmain( int argc, _TCHAR* argv[] )
{
HANDLE h1, h2;
h1 =
(HANDLE)_beginthread( &mywork1, 0, 0 );
h2 =
(HANDLE)_beginthread( &mywork2, 0, 0 );
WaitForSingleObject( h1, INFINITE );
WaitForSingleObject( h2, INFINITE );
}
The routine mywork1() in Listing 6.4 terminates quickly and may have already ter-minated by
the time that the main thread reaches the call to create the second thread. If
the first thread has terminated, the handle to the first thread may be reused
as the handle to the second thread. Queries using the handle of the first
thread might succeed, but they will work on the wrong thread. In the code shown
in Listing 6.4, the calls to WaitForSingleObject() may not be using a correct or valid handle for either of the
threads depending on the completion time of the
threads.
Listing 6.5 shows an equivalent code that uses _beginthreadex(). Threads
created with _beginthreadex() need to
be cleaned up with a call to CloseHandle().
Consequently, the calls to WaitForSingleObject() are
certain to get the correct handles.
Listing 6.5 Creating Threads
Using _beginthreadex() to Ensure That Handles Are Not Reused
#include <windows.h> #include
<process.h>
unsigned int __stdcall mywork1(
void * data )
{
return 0;
}
unsigned int __stdcall mywork2(
void * data )
{
volatile int i;
for (i=0; i<100000; i++)
{} // because i
is volatile most compilers will not // eliminate the loop
return 0;
}
int _tmain( int argc, _TCHAR* argv[] )
{
HANDLE h1, h2;
h1 =
(HANDLE)_beginthreadex( 0, 0, &mywork1, 0, 0, 0);
h2 =
(HANDLE)_beginthreadex( 0, 0, &mywork2, 0, 0, 0);
WaitForSingleObject( h1, INFINITE );
WaitForSingleObject( h2, INFINITE );
CloseHandle( h1 );
CloseHandle( h2 );
}
Related Topics
Privacy Policy, Terms and Conditions, DMCA Policy and Compliant
Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.