My Training Period: xx hours. Before you begin, read someinstruction here.
The expected abilities:
C Run-Time Windows Threads and Multithread Applications: Story & Program Examples
Introduction
In this Module we will try to learn about process and thread in Windows based on usingMicrosoft C Run-Time library. Later on, more understandable about process and thread using Win32 APIs will be discussed thoroughly in another Module. Just grasp some idea here or you can skip this Module and directly jump to the other Module ofprocess and thread using win32 APIs. Keep in mind that most of the functions/routines used in Microsoft C are similar to the Standard C except the function names prefixed by underscore ( _ ). A thread is basically a path of execution through a program. It is also the smallest unit of execution that Win32 schedules. Basically a thread consists of a stack, the state of the CPU registers, and an entry in the execution list of the system scheduler. Each thread shares all of the process's resources. A process consists of one or more threads and the code, data, and other resources of a program in memory. Typical program resources are open files, semaphores, and dynamically allocated memory. A program executes when the system scheduler gives one of its threads execution control. The scheduler determines which threads should run and when they should run. Threads of lower priority may have to wait while higher priority threads complete their tasks. On multiprocessor machines, the scheduler can move individual threads to different processors to "balance" the CPU load. Each thread in a process operates independently. Unless you make them visible to each other, the threads execute individually and are unaware of the other threads in a process. Threads sharing common resources, however, must coordinate their work by using semaphores or another method of interprocess communication.
Multithreading with C and Win32
Microsoft Visual C++ provides support for creating multithread applications with 32-bit versions of Microsoft Windows: Windows XP, Windows 2000, Windows NT, Windows Me, and Windows 98. You should consider using more than one thread if your application needs to manage multiple activities, such as simultaneous keyboard and mouse input. One thread can process keyboard input while a second thread filters mouse activities. A third thread can update the display screen based on data from the mouse and keyboard threads. At the same time, other threads can access disk files or get data from a communications port. With Visual C++, there are two ways to program with multiple threads:
We will concentrate on using Microsoft C in this Module and after that Win32 APIs. MFC will not be discussed because there is a lot of information about it out there furthermore Tenouk doesn’t like GUI :o)
|
C Run-Time Libraries Compatibility
The Microsoft C run-time library supports American National Standards Institute (ANSI) C and UNIX C. In this documentation, references to UNIX include XENIX, other UNIX-like systems, and the POSIX subsystem in Windows 98, Windows ME, Windows NT, Windows 2000, and Windows XP. The description of each run-time library routine inMSDN documentation includes a compatibility section for the following targets:
Target | Abbreviation |
ANSI | ANSI |
Windows 98 | Win 98 |
Windows Me | Win Me |
Windows NT | Win NT |
Windows 2000 | Win 2000 |
Windows XP | Win XP |
Next Windows... | ... |
Table 1 |
All C run-time library routines that included in Visual C++/. Net also compatible with the Win32 API. In this Module and that follows we will concentrate using C run-time library and Win32 API will be discussed in next Module.
Writing a MultithreadedWin32 Program
When you write a program with multiple threads, you must coordinate their behavior and use of the program's resources. You must also make sure that each thread receives its own stack.
Each thread has its own stack and its own copy of the CPU registers. Other resources, such as files, static data, and heap memory, are shared by all threads in the process. Threads using these common resources must be synchronized. Win32 provides several ways to synchronize resources, including semaphores, critical sections, events, and mutexes. You will learn the detail of these things in next Module.When multiple threads are accessing static data, your program must provide for possible resource conflicts. Consider a program where one thread updates a static data structure containing (x, y) coordinates for items to be displayed by another thread. If the update thread alters the x coordinate and is preempted before it can change the y coordinate, the display thread may be scheduled before the y coordinate is updated. The item would be displayed at the wrong location. You can avoid this problem by using semaphores to control access to the structure.
A mutex (short for mutual exclusion) is a way of communicating among threads or processes that are executing asynchronously of one another. This communication is usually used to coordinate the activities of multiple threads or processes, typically by controlling access to a shared resource by "locking" and "unlocking" the resource. For example, to solve the (x, y) coordinate update problem, the update thread would set a mutex indicating that the data structure is in use before performing the update. It would clear the mutex after both coordinates had been processed. The display thread must wait for the mutex to be clear before updating the display. This process of waiting for a mutex is often called "blocking" on a mutex because the process is blocked and cannot continue until the mutex clears.
All of an application's default stack space is allocated to the first thread of execution, which is known as thread 1. As a result, you must specify how much memory to allocate for a separate stack for each additional thread your program needs. The operating system will allocate additional stack space for the thread, if necessary, but you must specify a default value.
Threads that make calls to the C run-time library or to the Win32 API must allow sufficient stack space for the library and API functions they call. The C printf() function for example, requires more than 500 bytes of stack space, and you should have 2K of stack space available when calling Win32 API routines. Because each thread has its own stack, you can avoid potential collisions over data items by using as little static data as possible. Design your program to use automatic stack variables for all data that can be private to a thread.
We try to get some brief idea about multithreading in MFC in this section and the detail will not be discussed here. The Microsoft Foundation Class Library (MFC) provides support for multithreaded applications. A "process" is an executing instance of an application. For example, when you double-click the Notepad icon, you start a process that runs Notepad. Again, a "thread" is a path of execution within a process. When you start Notepad, the operating system creates a process and begins executing the primary thread of that process. When this thread terminates, so does the process. This primary thread is supplied to the operating system by the startup code in the form of a function address. Usually, it is the address of the main() or WinMain() function that is supplied. You can create additional threads in your application if you wish. You may want to do this to handle background or maintenance tasks when you don't want the user to wait for them to complete. All threads in MFC applications are represented by CWinThread() objects. In most situations, you don't even have to explicitly create these objects; instead call the framework helper function AfxBeginThread(), which creates the CWinThread() object for you. MFC distinguishes two types of threads:
User-interface threads are commonly used to handle user input and respond to events and messages generated by the user. Worker threads are commonly used to complete tasks, such as recalculation, that do not require user input. The Win32 API does not distinguish between types of threads; it just needs to know the thread's starting address so it can begin to execute the thread. MFC handles user-interface threads specially by supplying a message pump for events in the user interface. CWinApp is an example of a user-interface thread object, as it derives fromCWinThread and handles events and messages generated by the user.Special attention should be given to situations where more than one thread may require access to the same object. Writing and debugging multithreaded programming is inherently a complicated and tricky undertaking, as you must ensure that objects are not accessed by more than one thread at a time.
As an example, if one thread is suspended by the Win32 scheduler while executing the printf() function, one of the program's other threads might start executing. If the second thread also callsprintf(), data might be corrupted. To avoid this situation, access to static data used by the function must be restricted to one thread at a time. You do not need to serialize access to stack-based (automatic) variables because each thread has a different stack. Therefore, a function that uses only automatic (stack) variables is reentrant. The standard C run-time libraries, such asLIBC, have a limited number of reentrant functions. A multithread program needing to use C run-time library functions that are normally not reentrant should be built with the multithread library LIBCMT.LIB.
The support library LIBCMT.LIB is a reentrant library for creating multithread programs. TheMSVCRT.LIB library, which calls code in the shared MSVCRT70.DLL, is also reentrant. When your application calls functions in these libraries, the following rules may apply:
All library calls must use the C (__cdecl) calling convention; programs compiled using other calling conventions (such as __fastcall or__stdcall) must use the standard include files for the run-time library functions they call.
Variables passed to library functions must be passed by value or cast to a pointer.
Programs built with LIBCMT.LIB do not share C run-time library code or data with any dynamic-link libraries they call.
If you build a multithread program without using LIBCMT.LIB, you must do the following:
Warning: The multithread library LIBCMT.LIB includes the _beginthread() and _endthread() functions. The _beginthread() function performs initialization without which many C run-time functions will fail. You must use_beginthread() instead of CreateThread() in C programs built with LIBCMT.LIB if you intend to call C run-time functions.
To build a multithread application that uses the C run-time libraries, you must tell the compiler to use a special version of the libraries (LIBCMT.LIB). To select these libraries:
First open the project's Property Pages dialog box (View menu) and click the C/C++ folder. Select the Code Generation page. From the Runtime Library drop-down box, select Multi-threaded. Click OK to return to editing.
|
From the command line, the Multithread Library compiler option (/MT) is the best way to build a multithread program with LIBCMT.LIB. This option, which is automatically set when you specify a multithreaded application when creating a new project, embeds the LIBCMT library name in the object file.
Process and Environment Control
Use the process-control routines to start, stop, and manage processes from within a program. Use the environment-control routines to get and change information about the operating-system environment.
C Run-time: Process and Environment Control Functions
The following Table lists the process and environment functions that can be used. Take note that most of the functions also available in Standard C with the same function name except prefixed by the underscore ( _ ). |
Routine | Use |
abort() | Abort process without flushing buffers or calling functions registered by atexit() and _onexit(). |
assert() | Test for logic error |
_ASSERT,_ASSERTE macros | Similar to assert(), but only available in the debug versions of the run-time libraries. |
atexit() | Schedule routines for execution at program termination. |
_beginthread(),_beginthreadex() | Create a new thread on a Windows operating system process. |
_cexit() | Perform exit termination procedures (such as flushing buffers), then return control to calling program without terminating process. |
_c_exit() | Perform _exit() termination procedures, then return control to calling program without terminating process. |
_cwait() | Wait until another process terminates. |
_endthread(),_endthreadex() | Terminate a Windows operating system thread. |
_execl(),_wexecl() | Execute new process with argument list. |
_execle(),_wexecle() | Execute new process with argument list and given environment. |
_execlp(),_wexeclp() | Execute new process using PATH variable and argument list. |
_execlpe(),_wexeclpe() | Execute new process using PATH variable, given environment, and argument list. |
_execv(),_wexecv() | Execute new process with argument array. |
_execve(),_wexecve() | Execute new process with argument array and given environment. |
_execvp(),_wexecvp() | Execute new process using PATH variable and argument array. |
_execvpe(),_wexecvpe() | Execute new process using PATH variable, given environment, and argument array. |
exit() | Call functions registered byatexit() and _onexit(), flush all buffers, close all open files, and terminate process. |
_exit() | Terminate process immediately without calling atexit() or _onexit() or flushing buffers. |
getenv(),_wgetenv() | Get value of environment variable. |
_getpid() | Get process ID number. |
longjmp() | Restore saved stack environment; use it to execute a non-local goto. |
_onexit() | Schedule routines for execution at program termination; use for compatibility with Microsoft C/C++ version 7.0 and earlier. |
_pclose() | Wait for new command processor and close stream on associated pipe. |
perror(),_wperror() | Print error message. |
_pipe() | Create pipe for reading and writing. |
_popen(),_wpopen() | Create pipe and execute command. |
_putenv(),_wputenv() | Add or change value of environment variable. |
raise() | Send signal to calling process. |
setjmp() | Save stack environment; use to execute non-local goto. |
signal() | Handle interrupt signal. |
_spawnl(),_wspawnl() | Create and execute new process with specified argument list. |
_spawnle(),_wspawnle() | Create and execute new process with specified argument list and environment. |
_spawnlp(),_wspawnlp() | Create and execute new process usingPATH variable and specified argument list. |
_spawnlpe(),_wspawnlpe() | Create and execute new process usingPATH variable, specified environment, and argument list. |
_spawnv(),_wspawnv() | Create and execute new process with specified argument array. |
_spawnve(),_wspawnve() | Create and execute new process with specified environment and argument array. |
_spawnvp(),_wspawnvp() | Create and execute new process usingPATH variable and specified argument array. |
_spawnvpe(),_wspawnvpe() | Create and execute new process usingPATH variable, specified environment, and argument array. |
system(), _wsystem() | Execute operating-system command. |
Table 2 |
In Windows 98/Me and Windows NT/2000/XP, the spawned process is equivalent to the spawning process. Any process can use _cwait() to wait for any other process for which the process ID is known. The difference between the _exec() and_spawn() families is that a _spawn() function can return control from the new process to the calling process. In a_spawn() function, both the calling process and the new process are present in memory unless _P_OVERLAY is specified. In an _exec() function, the new process overlays the calling process, so control cannot return to the calling process unless an error occurs in the attempt to start execution of the new process. The differences among the functions in the_exec() family, as well as among those in the _spawn() family, involve the method of locating the file to be executed as the new process, the form in which arguments are passed to the new process, and the method of setting the environment, as shown in the following table. Use a function that passes an argument list when the number of arguments is constant or is known at compile time. Use a function that passes a pointer to an array containing the arguments when the number of arguments is to be determined at run time. The information in the following table also applies to the wide-character counterparts of the_spawn() and _exec() functions.
_exec(),_wexec() Family
Each function in this family loads and executes a new process. The_w is for wide-character version.
_exec(), _wexec() Family | |
_execl(),_wexecl() | _execv(),_wexecv() |
_execle(),_wexecle() | _execve(),_wexecve() |
_execlp(),_wexeclp() | _execvp(),_wexecvp() |
_execlpe(),_wexeclpe() | _execvpe(),_wexecvpe() |
Table 3 |
The letter(s) at the end of the function name determine the variation.
_exec function suffix | Description |
e | envp, array of pointers to environment settings, is passed to new process. |
l | Command-line arguments are passed individually to _exec() function. Typically used when number of parameters to new process is known in advance. |
p | PATH environment variable is used to find file to execute. |
v | argv, array of pointers to command-line arguments, is passed to _exec(). Typically used when number of parameters to new process is variable. |
Table 4 |
Each of the _exec() functions loads and executes a new process. All _exec() functions use the same operating-system function. The _exec() functions automatically handle multibyte-character string arguments as appropriate, recognizing multibyte-character sequences according to the multibyte code page currently in use. The _wexec() functions are wide-character versions of the _exec() functions. The _wexec() functions behave identically to their _exec() family counterparts except that they do not handle multibyte-character strings. The following Table lists the needed information in order to use the _exec() functions family.
Information | Description |
The function | _exec() family. |
The use | Load and execute new child processes. |
The prototype | intptr_t _execl(const char *cmdname, const char *arg0, ... const char *argn, NULL); intptr_t _wexecl(const wchar_t *cmdname, const wchar_t *arg0, ... const wchar_t *argn, NULL);
intptr_t _execle(const char *cmdname, const char *arg0, ... const char *argn, NULL, const char *const *envp); intptr_t _wexecle(const wchar_t *cmdname, const wchar_t *arg0, ... const wchar_t *argn, NULL, const char *const *envp);
intptr_t _execlp(const char *cmdname, const char *arg0, ... const char *argn, NULL); intptr_t _wexeclp(const wchar_t *cmdname, const wchar_t *arg0, ... const wchar_t *argn, NULL);
intptr_t _execlpe(const char *cmdname, const char *arg0, ... const char *argn, NULL, const char *const *envp); intptr_t _wexeclpe(const wchar_t *cmdname, const wchar_t *arg0, ... const wchar_t *argn, NULL, const wchar_t *const *envp);
intptr_t _execv(const char *cmdname, const char *const *argv); intptr_t _wexecv(const wchar_t *cmdname, const wchar_t *const *argv);
intptr_t _execve(const char *cmdname, const char *const *argv, const char *const *envp); intptr_t _wexecve(const wchar_t *cmdname, const wchar_t *const *argv, const wchar_t *const *envp);
intptr_t _execvp(const char *cmdname, const char *const *argv); intptr_t _wexecvp(const wchar_t *cmdname, const wchar_t *const *argv);
intptr_t _execvpe(const char *cmdname, const char *const *argv, const char *const *envp); intptr_t _wexecvpe(const wchar_t *cmdname, const wchar_t *const *argv, const wchar_t *const *envp); |
Example |
|
The parameters | cmdname - Path of file to be executed. arg0, ... argn - List of pointers to parameters. envp - Array of pointers to environment settings. argv - Array of pointers to parameters. |
The return value | See below. |
The header file | _execl(),_execle(),_execlp(),_execlpe(),_execv(),_execve(),_execvp(),_execvpe() - <process.h>. _wexecl(),_wexecle(),_wexeclp(),_wexeclpe(),_wexecv(),_wexecve(),_wexecvp(),_wexecvpe() - <process.h> or <wchar.h> |
Remarks | Each of these functions loads and executes a new process, passing each command-line argument as a separate parameter. |
Table 5: _exec() function family information. |
Return Value
If successful, these functions do not return to the calling process. A return value of –1 indicates an error, in which case the errno global variable is set.
errno value | Description |
E2BIG | The space required for the arguments and environment settings exceeds 32 K. |
EACCES | The specified file has a locking or sharing violation. |
EMFILE | Too many files open (the specified file must be opened to determine whether it is executable). |
ENOENT | File or path not found. |
ENOEXEC | The specified file is not executable or has an invalid executable-file format. |
ENOMEM | Not enough memory is available to execute the new process; or the available memory has been corrupted; or an invalid block exists, indicating that the calling process was not allocated properly. |
Table 6 |
Generic-Text Function Mappings
The following Table lists the generic-text functions mapping available and used in Microsoft C.
TCHAR.H routine | _UNICODE & _MBCS not defined | _MBCS defined | _UNICODE defined |
_texecl() | _execl() | _execl() | _wexecl() |
_texecle() | _execle() | _execle() | _wexecle() |
_texeclp() | _execlp() | _execlp() | _wexeclp() |
_texeclpe() | _execlpe() | _execlpe() | _wexeclpe() |
_texecv() | _execv() | _execv() | _wexecv() |
_texecve() | _execve() | _execve() | _wexecve() |
_texecvp() | _execvp() | _execvp() | _wexecvp() |
_texecvpe() | _execvpe() | _execvpe() | _wexecvpe() |
Table 7 |
When a call to an _exec() function is successful, the new process is placed in the memory previously occupied by the calling process. Sufficient memory must be available for loading and executing the new process. The cmdname parameter specifies the file to be executed as the new process. It can specify a full path (from the root), a partial path (from the current working directory), or a filename. Ifcmdname does not have a filename extension or does not end with a period (.), the _exec() function searches for the named file.
If the search is unsuccessful, it tries the same base name with the .com extension and then with the .exe, .bat, and .cmd extensions. If cmdname has an extension, only that extension is used in the search. Ifcmdname ends with a period, the _exec() function searches for cmdname with no extension. _execlp(),_execlpe(),_execvp(), and _execvpe() search for cmdname (using the same procedures) in the directories specified by thePATH environment variable. If cmdname contains a drive specifier or any slashes (that is, if it is a relative path), the _exec() call searches only for the specified file; the path is not searched.Parameters are passed to the new process by giving one or more pointers to character strings as parameters in the _exec() call. These character strings form the parameter list for the new process. The combined length of the inherited environment settings and the strings forming the parameter list for the new process must not exceed 32K bytes. The terminating null character ('\0') for each string is not included in the count, but space characters (inserted automatically to separate the parameters) are counted.
The argument pointers can be passed as separate parameters (in _execl(),_execle(),_execlp(), and _execlpe()) or as an array of pointers (in _execv(), _execve(),_execvp(), and _execvpe()). At least one parameter, arg0, must be passed to the new process; this parameter is argv[0] of the new process. Usually, this parameter is a copy of cmdname. (A different value does not produce an error.)The _execl(), _execle(),_execlp(), and _execlpe() calls are typically used when the number of parameters is known in advance. The parameterarg0 is usually a pointer to cmdname. The parameters arg1 throughargn point to the character strings forming the new parameter list. A null pointer must follow argn to mark the end of the parameter list.The _execv(), _execve(),_execvp(), and _execvpe() calls are useful when the number of parameters to the new process is variable. Pointers to the parameters are passed as an array, argv. The parameterargv[0] is usually a pointer to cmdname. The parameters argv[1] throughargv[n] point to the character strings forming the new parameter list. The parameterargv[n+1] must be a NULL pointer to mark the end of the parameter list.Files that are open when an _exec() call is made remain open in the new process. In _execl(), _execlp(),_execv(), and _execvp() calls, the new process inherits the environment of the calling process._execle(),_execlpe(),_execve(), and _execvpe() calls alter the environment for the new process by passing a list of environment settings through the envp parameter. envp is an array of character pointers, each element of which (except for the final element) points to a null-terminated string defining an environment variable. Such a string usually has the form:
NAME=value
Where NAME is the name of an environment variable and value is the string value to which that variable is set. (Note that value is not enclosed in double quotation marks.) The final element of theenvp array should be NULL. When envp itself is NULL, the new process inherits the environment settings of the calling process. A program executed with one of the _exec() functions is always loaded into memory as if the maximum allocation field in the program's.exe file header were set to the default value of 0xFFFFH.
The _exec() calls do not preserve the translation modes of open files. If the new process must use files inherited from the calling process, use the _setmode() routine to set the translation mode of these files to the desired mode. You must explicitly flush (usingfflush() or _flushall()) or close any stream before the _exec() function call. Signal settings are not preserved in new processes that are created by calls to_exec() routines. The signal settings are reset to the default in the new process. Let try some working program examples.
Further reading and digging:
Microsoft C references, online MSDN.
Microsoft Visual C++, online MSDN.
ReactOS - Windows binary compatible OS - C/C++ source code repository, Doxygen.
Linux Access Control Lists (ACL) info can be found atAccess Control Lists.
Check the best selling C / C++ and Windows books at Amazon.com.