Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Multitasking Fortran and Windows NT


SP93: Multitasking Fortran and Windows NT

Shankar is with Microsoft Corporation and can be reached at One Microsoft Way, Redmond, WA 98052. His interests include multiprocessor and remote procedure-call programming technologies.


AWindows NT application can consist of more than one process, and a process can consist of more than one thread. The Win32 API supports multitasking, which allows the simultaneous execution of multiple threads and processes. In a single-processor system, multitasking is achieved by dividing the CPU time among all threads competing for it. With systems having multiple processors and symmetric multiprocessing, more than one thread or process can be executed simultaneously, resulting in a dramatic improvement in application performance.

However, NT applications that usually jive well in such environments are written in C and C++ because the Win32 APIs involve C-type character strings, null pointers, pointers to valid data types, structures, array of structures, cyclic/recursive structures, pointers to structures, and dynamic allocation of memory. Trying to develop Fortran apps to make use of these APIs can be a challenging and arduous task.

Numerically intensive Fortran apps, both existing and new, are suited to Windows NT because they naturally yield to subdivision of computational tasks. Matrix computations, solutions of linear algebraic equations, partial differential equations, interpolations and extrapolations, integration and evaluation of functions, Eigen systems, Fourier and fast Fourier transformations, and statistical simulation and modeling are typical of this divide-and-conquer paradigm. Some of these functions are inherently parallelizable and traditionally run on mainframes and supercomputers. With a 32-bit, flat memory-model operating system like Windows NT, however, all these applications can run on a PC. With the guidelines and interface-statement file provided in this article, you can write Fortran apps that call the Win32 API directly, gaining all the benefits of its multitasking and multiprocessing abilities.

Processes and Threads

A process can be considered as a program loaded into memory and prepared for execution. Each process has a private virtual-address space and consists of code, data, and other system resources. Threads, on the other hand, are the basic entity to which the operating system allocates CPU time. Each process is started with a single thread, but additional, independently executing threads can be created. Each thread maintains a set of structures for saving its context while waiting to be scheduled for processing time. The context includes the thread's set of machine

registers, the kernel stack, a thread-

environment block, and a user stack in the address space of the thread's process. The most important feature of threads is that all threads of a process share the same virtual-address space, and can access global variables (like the Fortran common block) and system resources of the process. This makes communication between threads easy and cheap. Furthermore, the system can create and execute threads more quickly than it creates processes. The code for threads has already been mapped into the address space of the process, whereas the code for the new process must be loaded during run time. In addition, all threads of a process can use open handles to resources such as files and pipes. Hence, it's usually more efficient for an application to implement multitasking by distributing tasks among the threads of one process rather than by creating multiple processes.

Time Slicing

The Win32 API in Windows NT is designed for preemptive multitasking. Under preemptive multitasking, the system allocates small slices of CPU time among the competing threads. The currently executing thread is suspended when its time slice elapses, allowing another thread to run. When the system switches from one thread to another, it saves the context of the suspended thread and restores the saved context of the next thread in queue. To the application developer, the advantage of multitasking is the ability to create applications that use more than one process and to create processes that use more than one thread of execution.

If, for example, you make a simple Fortran app, like matrix multiplication, multithreaded, you can create separate threads for multiplying every row with a particular column. Because each time slice is small, it may appear that multiple threads are multiplying the subcomponents of the matrix simultaneously. This is true on multiprocessor systems, where the executable threads are distributed among the available

processors.

Thread Creation

The Win32 API CreateThread creates a new thread for a process. Example 1(a) shows how this API function is prototyped in winbase.h (shipped with the NT SDK). Looking at the listing of kernel32.lib, you'll notice that this function is listed as _CreateThread@24. This Win32 API is invoked with the __stdcall convention, which means that all the function arguments are pushed on the stack, and the stack is cleaned up by the callee. The __stdcall function names are prefixed by an underscore and suffixed with @<number> when decorated. The number is the number of bytes in decimal used by the widened arguments pushed on the stack.

CreateThread returns a HANDLE which is an integer*4 (double word) entity in Fortran. The creating thread must specify the starting address of the code that the new thread is to execute. The loc function in Microsoft Fortran can provide the address of variables as well as functions. By default, all parameters are passed by value in C, and by reference in Fortran. Since all the functions are external by specification in Fortran, declaring the function in Example 1(a) as external isn't necessary. A process can have multiple threads simultaneously executing the same function. The arguments specifying the stack size of the new thread and the Creation Flags are double words in C, and they are once again integer*4 data types in Fortran. In the function prototype in Example 1(a), the argument to the thread function is passed through a long pointer. On the Fortran side, this object can be passed by reference; this will pass a long pointer (integer*4) to that object. CreateThread returns the identifier of the thread through a long pointer to a double word, and, on the Fortran side, that parameter can be specified as integer*4 with the reference attribute. The first argument to CreateThread is a structure prototyped in winbase.h, as in Example 1(b). This structure can be implemented using STRUCTURE/END STRUCTURE statements in Fortran, as in Example 1(c). Note that BOOL in C is a logical*4 in Fortran capable of taking either a .TRUE or .FALSE value. Since the parameter in the C-function prototype is a long pointer to the structure, the structure itself can be passed by reference, or the loc of the structure can be passed by value in Fortran. The same is true for character strings. Passing the loc of the structure or character string has a distinct advantage because if I want to pass a C null point

er, I can simply pass a 0 in Fortran.

With all the arguments of CreateThread squared away, the interface statement can be specified as in Example 2.

Synchronization

In a multitasking environment, it's sometimes necessary to coordinate the execution of multiple processes or multiple threads within a process. Win32 provides a set of synchronization objects for this. A synchronization object is essentially a data structure whose current state is signaled or not-signaled. A thread can interact with any of these objects either by modifying its state or by waiting for it to be in a signaled state. When a thread waits for an object, the execution of the thread is blocked as long as the state of the object is not-

signaled. Typically, a thread will wait for a synchronization object before performing an operation that must coordinate with other threads; it will also wait when using a shared resource such as file, shared memory, or a peripheral device.

There are four types of synchronization objects: critical section, mutual exclusion (mutex), semaphores, and events. Two generic functions, WaitForSingleObject and WaitForMultipleObjects, are used by threads to wait for the state of a waitable object to be signaled. In addition to event, mutex, and semaphore objects, these functions may be used to wait for process and thread objects. The prototypes for WaitForSingleObject and WaitForMultipleObjects are provided in winbase.h in the NT SDK; the interface statements for them are provided in mt.fi, Listing One (page 25).

Critical Section

A critical section is a synchronization object that can be owned by only one thread at a time, enabling threads to coordinate mutually exclusive access to a shared resource. The restriction on this object is that it can only be used by threads of a single process. The critical-section object is a cyclic data structure, which makes its representation interesting and challenging in Fortran; winnt.h (in the NT SDK) declares the structure, as in Example 3(a). Example 3(b) is the Fortran implementation of the cyclic structure in Example 3(a). The loc function points the first structure to the second, and the second structure back to the first. Although the LIST_ENTRY item in the C typedef statement could be complex, I don't need to go into the implementation details in Fortran, because all that's required is a 4-byte space for the address of that data structure.

To illustrate, I'll develop code for finding the sum of the first 50 whole numbers, and apply various facets of multitasking to it. I'll start by generating 50 threads, each passing a particular value to ThreadFunc. Each of the threads adds its value to a global variable, result, which is inside a common block. Since you shouldn't allow simultaneous access to the global variable by all the threads, I protect this resource inside a critical section. This calls for an initialization of the critical-section object (done by InitializeCriticalSection), and the modification of the global variable result is enclosed within EnterCriticalSection and LeaveCriticalSection.

However, if the primary thread exits before the completion of all the other threads, the child threads are "orphaned," and hence we wait for all the threads to complete through WaitForMultipleObjects. I've made the function wait on the handle to all the threads indefinitely until all the threads complete their execution. The critical section object, GlobalCriticalSection, is also inside the common block so that it need not be passed as a parameter to ThreadFunc. The code is given in Listing Three (page 26). Also refer to the include file mt.fd (Listing Two, page 26) for data-type declarations.

Mutex, Semaphore, and Events

A mutex (mutual exclusion) is similar to a critical-section object except that it can be used by the threads belonging to more than one process. A semaphore object is used as a resource gate and maintains a count between 0 and some maximum value, thus limiting the use of a resource by counting threads as they pass in and out of the gate. The Win32 API calls associated with mutex are CreateMutex, OpenMutex, and ReleaseMutex; there's a similar set for semaphores. The semaphore functions typically take an additional set of parameters that manipulate the semaphore count. The semaphores are quite powerful, since they are mutexes with the additional ability to control the number of threads. The Fortran prototype for these APIs are provided as interface statements in mt.fi (Listing One).

As another example, I'll modify the previous example to incorporate semaphores and mutex objects. I'll also try to save space by not requiring that you save the handles of all the threads waiting on them. Here, I generate 50 threads as before, and enclose the global common-variable result within the mutex region. Instead of waiting on all the threads to complete, however, I wait for the last thread to complete; this is an indication of all the threads having completed. To this end, a semaphore object is created with an initial count of 0. Since this is a not-signaled state of the semaphore, the call to WaitForSingleObject blocks the main thread until the last spawned thread releases the semaphore by incrementing the semaphore count by 1. The handles to the mutex and semaphore objects are hMutex and hSemaphore, respectively, and they're inside the common block so that they need not be passed as parameters. ThreadCounter is an additional parameter in the common block to keep track of the number of threads that have modified the global result variable. See Listing Four (page 27).

You can use an event object to trigger execution of other processes or other threads within a process. This is useful if one process provides data to many other processes. Using an event object frees the other processes from the trouble of polling to determine when new data is available. CreateEvent creates either a manual reset event or an auto reset event, depending on the value of one of its parameters. CreateEvent also sets the initial state of the event to either signaled (True) or not-signaled (False) state. When an event is not-signaled, any thread waiting on the event will block. You can set an event to the signaled state by calling SetEvent, and reset to the not-signaled state by calling ResetEvent. PulseEvent sets the event to the signaled state and then immediately resets it to the not-signaled state. (I've used some of the APIs related to events in the following process-creation example.)

Process Creation

CreateProcess creates a new process that runs independently of the creating process. CreateProcess allows you to name the program to execute by specifying either the pathname of the image file or a command line. This particular API call is prototyped, as in Example 4.

In the kernel32 library, there are two occurrences of this function: _CreateProcessA@40 and _CreateProcessW@40. All calls that take a character string as at least one of their parameters are decorated with the trailing A (for ASCII) or W (for wide character, or Unicode). The Unicode implementation addresses the problem of multiple-character coding schemes and accommodates a more comprehensive set of characters.

CreateProcess takes a long pointer to a C string as two of its arguments. In Fortran, the loc values of the string can be passed to this function, and the arguments can be declared in the interface statement as being passed by value. Since these two are C strings, they should have a null terminator or a char(0) at the end of the Fortran string. The creation-flags argument to CreateProcess can control the way in which the process is created, for instance, whether it is a detached process or a suspended process. It is a DWORD in C, and an integer*4 value in Fortran. The last two parameters of this function call are long pointers to structures. The structures are STARTUPINFO and PROCESS_INFORMATION, and they are typedefined in winbase.h; I've transliterated them into Fortran structures in mt.fd (Listing Two). The STARTUPINFO structure requires initialization, and one of the members of this structure is initialized to the size of that structure. The C sizeof function can be implemented in Fortran by dynamically creating a two-element array of the structure and subtracting the loc value of the first element from that of the second. However, I simply counted the number of bytes in that structure and specified it in the program.

A child process can inherit the following properties and resources from its parent:

  • Open handles that were opened with the inherit flag set to TRUE. The functions that create or open object handles (CreateEvent, CreateFile, CreateMutex, CreateNamedPipe, CreatePipe, CreateProcess, CreateThread,_) take a security-attributes argument that includes this inherit flag. The mt.fd file (Listing Two) declares this structure in Fortran.
  • Environment variables.
  • Current directory.
  • The I/O buffers for console applications (stdin and stdout).

Using Named Objects

CreateProcess may allow sharing of its object handles through their names. In the following example, the parent process creates a couple of handles to event objects with ReadEvent and WriteEvent as object names, and passes these names as command-line arguments to the child process. The child process retrieves these arguments using the Microsoft Fortran Getarg runtime function and uses the same names to open the handles to these objects. The names for each type of object exist in their own flat address space, and so a semaphore object could have the same name as a mutex object without collision. The child process usually specifies the desired access to the object. In this case, the child accesses the object with the attribute EVENT_ALL_ACCESS. This value is calculated by calling IOR (the Microsoft Fortran Inclusive OR function) on STANDARD_RIGHTS_REQUIRED, SYNCHRONIZE, and 3h (0x3 in C and #3 in Microsoft Fortran).

The parent and child processes execute simultaneously after the CreateProcess API call. However, the child process blocks at the WriteEvent until the parent writes the question on to the file named file.out. The parent then sets the WriteEvent, which is a green light for the child process. Subsequently, the parent process blocks at ReadEvent and waits for the cue from the child. The child opens the file, reads the question, writes its reply to the same file, and then sets the ReadEvent object, thus activating the parent process. The parent process then opens the file to read the answer given by the child process and writes it on the screen. The parent program is in Listing Five (page 27) and the child in Listing Six (page 27).

Inheriting Handles

A child process can inherit an open handle to a synchronization object if the InheritHandle attribute (in the security-

attribute parameter) was set when the handle was created. The handle inherited by the child process has the same access as the parent's handle. The code fragment in Listing Seven (page 27) describes this aspect and provides the required initialization for the security-attribute parameter. Note that the child process has no OpenEvent calls, since the handles are inherited from the parent. To share an unnamed object between unrelated processes, the creating process must communicate the information necessary for the other process to duplicate the handle. Using DuplicateHandle, the duplicating process can then open its handle with the same or more restricted access than the original handle.

Conclusion

The C prototypes for the Win32 API can be found in the header files winbase.h and winnt.h shipped with Microsoft Win32 SDK for Windows NT. The functions are actually defined in kernel32.lib and ntdll.lib. The description for some of these APIs can be found in the Programmer's Reference: Overviews manual and the api32wh.hlp file shipped with NT SDK. All the programs listed here were compiled from the command line by invoking fl32.exe. This automatically links the object modules with the required libraries: libf.lib, libc.lib, ntdll.lib, and kernel32.lib.

In mt.fi, I've provided the interface statements for almost the entire set of Win32 APIs related to processes, threads, and synchronization, and the corresponding data-structure declarations are in mt.fd. This includes DuplicateHandle and other calls associated with attributes, priority, suspension, resumption, and termination of threads and processes. I've also written interface statements for all the APIs associated with thread local storage (TLS). With TLS, one thread can allocate an index that can be used by any thread of the process to store and retrieve a different value for each thread.

With mt.fi and other pointers provided in this article, you should be able to roll up your sleeves and create a killer multithreading/multitasking/multiprocessing Fortran application under Windows NT.

Example 1: (a) Prototype of CreateThread; (b) structure for security attributes; (c) implementing the security-attributes structure using STRUCTURE/END STRUCTURE.

(a)

HANDLE WINAPI CreateThread (
    LP_SECURITY_ATTRIBUTES lpThreadAttributes,
    DWORD  dwStackSize,
    LPTHREAD_START_ROUTINE  lpStartAddress,
    LPVOID  lpParameter,
    DWORD  dwCreationFlags,
    LPDWORD  lpThreadId
    );


(b)

typedef struct _SECURITY_ATTRIBUTES {
    DWORD nLength;
    LPVOID lpSecurityDescriptor;
    BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;


(c)

STRUCTURE /SECURITY_ATTRIBUTES/
    integer*4 length
    integer*4 lpSecurityDescriptor
    logical*4 bInheritHandle
END STRUCTURE

Example 2: Interface statement for CreateThread.

interface to integer*4 function CreateThread [stdcall, alias: '_CreateThread@24']
+   (security, stack, thread_func, arguments, flags, thread_id)
   integer*4  security, stack     [value]
   integer*4  thread_func [value] ! loc(thread_func) is passed by value
   integer*4  arguments   [reference]
   integer*4  flags       [value]
   integer*4  thread_id   [reference]
 end

Example 3: (a) winnt.h (in the NT SDK) declares the cyclic data structure; (b) Fortran implementation of the cyclic structure.

(a)

typedef struct _RTL_CRITICAL_SECTION_DEBUG {
        WORD   Type;
        WORD   CreatorBackTraceIndex;
        struct _RTL_CRITICAL_SECTION *CriticalSection;
        LIST_ENTRY ProcessLocksList;
        DWORD EntryCount;
        DWORD ContentionCount;
        DWORD Depth;
        PVOID OwnerBackTrace[ 5 ];
} RTL_CRITICAL_SECTION_DEBUG, *PRTL_CRITICAL_SECTION_DEBUG;

typedef struct _RTL_CRITICAL_SECTION {
        PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
        LONG LockCount;
        LONG RecursionCount;
        HANDLE OwningThrea   // from the thread's ClientId->UniqueThread
        HANDLE LockSemaphore;
        DWORD Reserved;
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;


(b)

STRUCTURE /RTL_CRITICAL_SECTION_DEBUG/
    integer*4 Type
    integer*4 CreatorBackTraceIndex
    integer*4 Address
    integer*4 ProcessLocksList
    integer*4 EntryCount
    integer*4 ContentionCount
    integer*4 Depth
    integer*4 OwnerBackTrace(5)
END STRUCTURE

STRUCTURE /RTL_CRITICAL_SECTION/
    integer*4 Address
    integer*4 LockCount
    integer*4 RecursionCount
    integer*4 OwningThread
    integer*4 LockSemaphore
    integer*4 Reserved
END STRUCTURE
record /RTL_CRITICAL_SECTION/ GlobalCriticalSection
record /RTL_CRITICAL_SECTION_DEBUG/ AuxCriticalSection

GlobalCriticalSection.Address = loc(AuxCriticalSection)
AuxCriticalSection.Address = loc(GlobalCriticalSection)

Example 4: CreateProcess prototype.

BOOL WINAPI CreateProcessA(
        LPCSTR lpApplicationName,
        LPCSTR lpCommandLine,
        LPSECURITY_ATTRIBUTES lpProcessAttributes,
        LPSECURITY_ATTRIBUTES lpThreadAttributes,
        BOOL bInheritHandles,
        DWORD dwCreationFlags,
        LPVOID lpEnvironment,
        LPSTR lpCurrentDirectory,
        LPSTARTUPINFOA lpStartupInfo,
        LPPROCESS_INFORMATION lpProcessInformation
);

[LISTING ONE]

(Text begins on page 21.)

 interface to integer*4 function CreateEvent
+    [stdcall, alias: '_CreateEventA@16']
+    (security, reset, init_state, string)
   integer*4 security [value]
   Logical*4 reset [value]
   Logical*4 init_state [value]
   integer*4 string [value]
 end

 interface to integer*4 function CreateMutex
+    [stdcall, alias: '_CreateMutexA@12']
+    (security, owner, string)
    integer*4 security [value]
    Logical*4 owner [value]
    integer*4 string [value]
  end

  interface to logical*4 function CreateProcess
 +    [stdcall, alias: '_CreateProcessA@40']
 +    (lpApplicationName, lpCommandLine, lpProcessAttributes,
 +    lpThreadAttributes, bInheritHandles, dwCreationFlags,
 +    lpEnvironment, lpCurrentDirectory, lpStartupInfo,
 +    lpProcessInformation)
    integer*4 lpApplicationName [value]
    integer*4 lpCommandLine [value]
    integer*4 lpProcessAttributes [value]
    integer*4 lpThreadAttributes [value]
    logical*4 bInheritHandles [value]
    integer*4 dwCreationFlags [value]
    integer*4 lpEnvironment [value]
    integer*4 lpCurrentDirectory [value]
    integer*4 lpStartupInfo [value]
    integer*4 lpProcessInformation [value]
  end

  interface to integer*4 function CreateSemaphore
 +    [stdcall, alias: '_CreateSemaphoreA@16']
 +    (security, InitialCount, MaxCount, string)
    integer*4 security [value]
    integer*4 InitialCount [value]
    integer*4 MaxCount [value]
    integer*4 string [value]
  end

  interface to integer*4 function CreateThread
 +    [stdcall, alias: '_CreateThread@24']
 +    (security, stack, thread_func,
 +    argument, flags, thread_id)
    integer*4 security [value]
    integer*4 stack [value]
    integer*4 thread_func [value]
    integer*4 argument [reference]
    integer*4 flags [value]
    integer*4 thread_id [reference]
  end

  interface to subroutine DeleteCriticalSection
 +    [stdcall, alias: '_DeleteCriticalSection@4'] (object)
    integer*4 object [value]
  end

  interface to logical*4 function DuplicateHandle
 +    [stdcall, alias: '_DuplicateHandle@28']
 +    (hSourceProcessHandle, hSourceHandle,
 +     hTargetProcessHandle, lpTargetHandle,
 +     dwDesiredAccess, bInheritHandle, dwOptions)
    integer*4 hSourceProcessHandle [value]
    integer*4 hSourceHandle [value]
    integer*4 hTargetProcessHandle [value]
    integer*4 lpTargetHandle [reference]
    integer*4 dwDesiredAccess [value]
    logical*4 bInheritHandle [value]
    integer*4 dwOptions [value]
  end

  interface to subroutine EnterCriticalSection
 +    [stdcall, alias: '_EnterCriticalSection@4'] (object)
    integer*4 object [value]
  end

  interface to subroutine ExitProcess
 +    [stdcall, alias: '_ExitProcess@4'] (ExitCode)
    integer*4 ExitCode [value]
  end

  interface to subroutine ExitThread
 +    [stdcall, alias: '_ExitThread@4'] (ExitCode)
    integer*4 ExitCode [value]
  end

  interface to integer*4 function GetCurrentProcess
 +   [stdcall, alias: '_GetCurrentProcess@0'] ()
  end

  interface to integer*4 function GetCurrentProcessId
 +   [stdcall, alias: '_GetCurrentProcessId@0'] ()
  end

  interface to integer*4 function GetCurrentThread
 +   [stdcall, alias: '_GetCurrentThread@0'] ()
  end

  interface to integer*4 function GetCurrentThreadId
 +   [stdcall, alias: '_GetCurrentThreadId@0'] ()
  end

  interface to logical*4 function GetExitCodeProcess
 +   [stdcall, alias: '_GetExitCodeProcess@8']
 +   (hProcess, lpExitCode)
    integer*4 hProcess [value]
    integer*4 lpExitCode [reference]
  end

  interface to logical*4 function GetExitCodeThread
 +   [stdcall, alias: '_GetExitCodeThread@8']
 +   (hThread, lpExitCode)
    integer*4 hThread [value]
    integer*4 lpExitCode [reference]
  end

  interface to integer*4 function GetLastError
 +    [stdcall, alias: '_GetLastError@0'] ()
  end

  interface to integer*4 function GetThreadPriority
 +   [stdcall, alias: '_GetThreadPriority@4'] (hThread)
    integer*4 hThread [value]
  end

  interface to logical*4 function GetThreadSelectorEntry
 +   [stdcall, alias: '_GetThreadSelectorEntry@12']
 +   (hThread, dwSelector, lpSelectorEntry)
    integer*4 hThread [value]
    integer*4 dwSelector [value]
    integer*4 lpSelectorEntry [value]    ! Pass loc of the struct
  end

  interface to subroutine InitializeCriticalSection
 +    [stdcall, alias: '_InitializeCriticalSection@4'] (object)
    integer*4 object [value]
  end

  interface to subroutine LeaveCriticalSection
 +    [stdcall, alias: '_LeaveCriticalSection@4'] (object)
    integer*4 object [value]
  end

  interface to integer*4 function OpenEvent
 +     [stdcall, alias: '_OpenEventA@12']
 +     (dwDesiredAccess, bInheritHandle, lpName)
    integer*4 dwDesiredAccess [value]
    logical*4 bInheritHandle [value]
    integer*4 lpName [value]
  end

  interface to integer*4 function PulseEvent
 +    [stdcall, alias: '_PulseEvent@4'] (hEvent)
    integer*4 hEvent [value]
  end

  interface to Logical*4 function ReleaseMutex
 +    [stdcall, alias: '_ReleaseMutex@4'] (handle)
    integer*4 handle [value]
  end

  interface to Logical*4 function ReleaseSemaphore
 +    [stdcall, alias: '_ReleaseSemaphore@12']
 +    (handle, ReleaseCount, LpPreviousCount)
    integer*4 handle [value]
    integer*4 ReleaseCount [value]
    integer*4 LpPreviousCount [reference]
  end

  interface to integer*4 function ResumeThread
 +   [stdcall, alias: '_ResumeThread@4'] (hThread)
    integer*4 hThread [value]
  end

  interface to integer*4 function SetEvent
 +    [stdcall, alias: '_SetEvent@4'] (handle)
    integer*4 handle [value]
  end

  interface to subroutine SetLastError
 +    [stdcall, alias: '_SetLastError@4'] (dwErrorCode)
    integer*4 dwErrorCode [value]
  end

  interface to logical*4 function SetThreadPriority
 +   [stdcall, alias: '_SetThreadPriority@8'](hThread, nPriority)
    integer*4 hThread [value]
    integer*4 nPriority [value]
  end

  interface to integer*4 function SuspendThread
 +   [stdcall, alias: '_SuspendThread@4'] (hThread)
    integer*4 hThread [value]
  end

  interface to logical*4 function TerminateProcess
 +   [stdcall, alias: '_TerminateProcess@8']
 +   (hProcess, uExitCode)
    integer*4 hProcess [value]

    integer*4 uExitCode [value]
  end

  interface to logical*4 function TerminateThread
 +   [stdcall, alias: '_TerminateThread@8']
 +   (hThread, dwExitCode)
    integer*4 hThread [value]
    integer*4 dwExitCode [value]
  end

  interface to integer*4 function TlsAlloc
 +   [stdcall, alias: '_TlsAlloc@0'] ()
  end

  interface to logical*4 function TlsFree
 +   [stdcall, alias: '_TlsFree@4'] (dwTlsIndex)
    integer*4 dwTlsIndex [value]
  end

  interface to integer*4 function TlsGetValue
 +   [stdcall, alias: '_TlsGetValue@4'] (dwTlsIndex)
    integer*4 dwTlsIndex [value]
  end

  interface to logical*4 function TlsSetValue
 +   [stdcall, alias: '_TlsSetValue@8'] (dwTlsIndex, lpTlsVal)
    integer*4 dwTlsIndex [value]
    integer*4 lpTlsVal [value]
  end

  interface to integer*4 function WaitForMultipleObjects
 +    [stdcall, alias: '_WaitForMultipleObjects@16']
 +    (Count, LpHandles, WaitAll, Mseconds)
    integer*4 Count [value]
    integer*4 LpHandles [reference]
    logical*4 WaitAll [value]
    integer*4 Mseconds [value]
  end

  interface to integer*4 function WaitForSingleObject
 +    [stdcall, alias: '_WaitForSingleObject@8']
 +    (handle, Mseconds)
    integer*4 handle [value]
    integer*4 Mseconds [value]
  end

[LISTING TWO]


PARAMETER (MAX_THREADS = 50)
PARAMETER (WAIT_INFINITE = -1)
PARAMETER (STANDARD_RIGHTS_REQUIRED = #F0000)
PARAMETER (SYNCHRONIZE = #100000)

STRUCTURE /PROCESS_INFORMATION/
    integer*4 hProcess
    integer*4 hThread
    integer*4 dwProcessId
    integer*4 dwThreadId
END STRUCTURE

STRUCTURE /RTL_CRITICAL_SECTION_DEBUG/
    integer*4 Type
    integer*4 CreatorBackTraceIndex
    integer*4 Address
    integer*4 ProcessLocksList
    integer*4 EntryCount
    integer*4 ContentionCount
    integer*4 Depth
    integer*4 OwnerBackTrace(5)
END STRUCTURE

STRUCTURE /RTL_CRITICAL_SECTION/
    integer*4 Address
    integer*4 LockCount
    integer*4 RecursionCount
    integer*4 OwningThread
    integer*4 LockSemaphore
    integer*4 Reserved
END STRUCTURE

STRUCTURE /SECURITY_ATTRIBUTES/
    integer*4 nLength
    integer*4 lpSecurityDescriptor
    logical*4 bInheritHandle
END STRUCTURE

STRUCTURE /STARTUPINFO/
    integer*4 cb
    integer*4 lpReserved
    integer*4 lpDesktop
    integer*4 lpTitle
    integer*4 dwX
    integer*4 dwY
    integer*4 dwXSize
    integer*4 dwYSize
    integer*4 dwXCountChars
    integer*4 dwYCountChars
    integer*4 dwFillAttribute
    integer*4 dwFlags
    integer*2 wShowWindow
    integer*2 cbReserved2
    integer*4 lpReserved2
END STRUCTURE

[LISTING THREE]


Program to demonstrate thread creation and critical section object
      include 'mt.fi'

Thread function as a subroutine
      subroutine ThreadFunc (param)
      include 'mt.fd'
      integer*4 param, result
      record /RTL_CRITICAL_SECTION/ GlobalCriticalSection
      record /RTL_CRITICAL_SECTION_DEBUG/ AuxCriticalSection
      common result, GlobalCriticalSection

Critical section region begins...
      Call EnterCriticalSection ( loc(GlobalCriticalSection))
          result = param + result

Critical section region ends...
      Call LeaveCriticalSection ( loc(GlobalCriticalSection))
      Call ExitThread(0)
      return
      end

Main program begins here
      program test
      include 'mt.fd'
      external ThreadFunc
      integer*4 ThreadHandle(MAX_THREADS), inarray(MAX_THREADS)
      integer*4 CreateThread, threadId
      integer*4 waitResult, WaitForMultipleObjects
      integer*4 loop, result
      record /RTL_CRITICAL_SECTION/ GlobalCriticalSection
      record /RTL_CRITICAL_SECTION_DEBUG/ AuxCriticalSection
      common result, GlobalCriticalSection

Creating the cyclic structure for the critical section object
      GlobalCriticalSection.Address = loc(AuxCriticalSection)
      AuxCriticalSection.Address = loc(GlobalCriticalSection)

      result = 0

Initializing critical section...
      Call InitializeCriticalSection(loc(GlobalCriticalSection))

      do loop = 1, MAX_THREADS
         inarray(loop)= loop
         write(*, '(1x, A, I3)') 'Creating Thread # ', loop
         ThreadHandle(loop) = CreateThread( 0, 0, loc(ThreadFunc),
inarray(loop), 0, threadId)
      end do

      write(*,*) 'Waiting for all the threads to complete ...'
      waitResult = WaitForMultipleObjects
     +   (MAX_THREADS, ThreadHandle, .TRUE. , WAIT_INFINITE)
      write(*, '(1x, A, I6, A, I10)' )
     +   'The sum of the first ', MAX_THREADS,' #s is ', result
      end

[LISTING FOUR]


Program to demostrate the semaphore and mutual exclusion objects
      include 'mt.fi'

The thread function begins here
      subroutine ThreadFunc (param)
      include 'mt.fd'
      integer*4 param, waitResult, WaitForSingleObject
      integer*4 ThreadCounter
      integer*4 result, hMutex, hSemaphore, PreviousCount
      logical*4 release, ReleaseMutex, ReleaseSemaphore
      common result, hMutex, hSemaphore, ThreadCounter

Mutual exclusion region begins here
      waitResult = WaitForSingleObject(hMutex, WAIT_INFINITE)

Modifying the global variables
          result = param + result
          ThreadCounter = ThreadCounter + 1

Release the sempahore if this is the last thread
          if (ThreadCounter .EQ. MAX_THREADS)
     +         release = ReleaseSemaphore(hSemaphore, 1, PreviousCount)

Mutual exclusion region ends here
      release = ReleaseMutex(hMutex)
      return
      end

Main program begins here
      program test
      include 'mt.fd'
      external ThreadFunc
      integer*4 ThreadHandle, threadId
      integer*4 CreateSemaphore, CreateThread, CreateMutex
      integer*4 waitResult, WaitForSingleObject
      integer*4 loop
      integer*4 result, hMutex, hSemaphore, ThreadCounter
      integer*4 inarray
      dimension inarray(MAX_THREADS)
      common result, hMutex, hSemaphore, ThreadCounter

Initializing the global variables
      ThreadCounter = 0
      result = 0
      hMutex = CreateMutex(0, .FALSE. , 0)
      hSemaphore = CreateSemaphore(0, 0, 1, 0)

      do loop = 1, MAX_THREADS
         inarray(loop)= loop
         write(*,*) "Generating Thread #", loop
         ThreadHandle = CreateThread( 0, 0, loc(ThreadFunc),
     +         inarray(loop), 0, threadId)
      end do

      write(*,*) 'Waiting for the semaphore release...'
      waitResult = WaitForSingleObject(hSemaphore, WAIT_INFINITE)
      write(*, '(1x, A, I4, A, I8)')
     +    'The sum of the first ', MAX_THREADS,' #s is', result
      end

[LISTING FIVE]


Parent Program (process) passing names of event objects to child process
      include 'mt.fi'

      program Parent
      include 'mt.fd'
      logical*4 procHandle, CreateProcess
      integer*4 CreateEvent, hReadEvent, hWriteEvent, SetEvent
      integer*4 waitResult, WaitForSingleObject
      character*255 buffer
      character*10 strReadEvent, strWriteEvent, FileName

      record /PROCESS_INFORMATION/ pi
      record /STARTUPINFO/ si

Initializing the strings
      strReadEvent = 'ReadEvent '
      strWriteEvent = 'WriteEvent '
      FileName = ' file.out '
      buffer = "child "//strReadEvent//strWriteEvent//FileName//" "C
      strReadEvent(10:10) = char(0)
      strWriteEvent(10:10) = char(0)

Initializing the STARTUPINFO structure
      si.cb = 56             ! sizeof (STARTUPINFO)
      si.lpReserved = 0
      si.lpDeskTop = 0
      si.lpTitle = 0
      si.dwFlags = 0
      si.cbReserved2 = 0
      si.lpReserved2 = 0

Creating Read and Write Event objects
      hReadEvent = CreateEvent(0, .FALSE., .FALSE., loc(strReadEvent))
      hWriteEvent = CreateEvent(0, .FALSE., .FALSE.,loc(strWriteEvent))


Spawning the child prcoess
      procHandle=CreateProcess(0,loc(buffer),0,0,.TRUE.,0,0,0,loc(si),loc(pi))

Providing a question for the child
      open (10, file= FileName)
      write(10, '(A)') "What issue of Dr. Dobb's is this?"
      close (10)

      write(*,*) 'Providing the green signal for child to continue...'
      waitResult = SetEvent(hWriteEvent)
      write(*,*) 'Waiting for the child to answer the question - '
      waitResult = WaitForSingleObject (hReadEvent, WAIT_INFINITE)

Writing the reply from the child on to the screen
      open (10, file= FileName)
      read(10, '(A)') buffer
      close (10)
      write(*,*) buffer
      end

[LISTING SIX]


Child program (process) accepting named objects from the parent
      include 'mt.fi'

      program ChildProcess
      include 'mt.fd'

      character*255 buffer
      character*100 filename, strReadEvent, strWriteEvent
      integer*4 hReadEvent, hWriteEvent, OpenEvent, SetEvent
      integer*2 status
      integer*4 EVENT_ALL_ACCESS
      integer*4 waitResult, WaitForSingleObject

Retrieving the first command line parameter which is the name of the ReadEvent
      Call Getarg (1, buffer, status)
      strReadEvent(1:status) = buffer(1:status)
      status = status+1
      strReadEvent(status:status) = char(0) ! to make it a C string

Retrieving the second command line parameter which is the name of the WriteEvent
      Call Getarg (2, buffer, status)
      strWriteEvent(1:status) = buffer(1:status)
      status = status+1
      strWriteEvent(status:status) = char(0) ! to make it a C string

Setting the access privilege for the child
      EVENT_ALL_ACCESS = IOR (STANDARD_RIGHTS_REQUIRED, SYNCHRONIZE)
      EVENT_ALL_ACCESS = IOR (EVENT_ALL_ACCESS, #3)

Opening handles for event objects passed from parent as named objects
      hReadEvent=OpenEvent(EVENT_ALL_ACCESS, .FALSE.,  loc(strReadEvent))
      hWriteEvent=OpenEvent(EVENT_ALL_ACCESS, .FALSE., loc(strWriteEvent))

Wait until the parent signals the WriteEvent
      waitResult = WaitForSingleObject(hWriteEvent, WAIT_INFINITE)

Retrieve the file name which is the third argument
      Call Getarg (3, buffer, status)
      filename (1:status) = buffer(1:status)

Read the parent's question and then reply
      open (11, file= filename, mode ='readwrite')
      read(11, '(A)') buffer
      print *, buffer
      rewind 11
      write(11, '(A)') 'September 1993 issue'
      close (11)

Signal the parent to continue
      waitResult = SetEvent(hReadEvent)
      end

[LISTING SEVEN]


A fragment of the parent program

 ...

Initialization of Security attributes for Read and Write Events
      record /SECURITY_ATTRIBUTES/ saR
      record /SECURITY_ATTRIBUTES/ saW

      saR.nLength = 12
      saR.lpSecurityDescriptor = 0
      saR.bInheritHandle = .TRUE.

      saW.nLength = 12
      saW.lpSecurityDescriptor = 0
      saW.bInheritHandle = .TRUE.

Creating events whose handles can be inherited
      hReadEvent = CreateEvent(loc(saR), .FALSE., .FALSE., 0)
      hWriteEvent = CreateEvent(loc(saW), .FALSE., .FALSE., 0)
 ...
--------------------------------------------------------------------------
 ...

A fragment of the child program.
Retrieve the handle to Read and Write Events from the command line using Getarg, and assign them to integer variables through Internal Read
      CALL GETARG(1, buffer, status)
      read(buffer(1:status), '(i4)') hReadEvent
      CALL GETARG(2, buffer, status)
      read(buffer(1:status), '(i4)') hWriteEvent

      waitResult = WaitForSingleObject(hWriteEvent, WAIT_INFINITE)
  ...

Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.