Sharing Resources

Each thread has its own stack and its own copy of the CPU registers. Other resources, such as files, units, static data, and heap memory, are shared by all threads in the process. Threads using these common resources must coordinate their work. There are several ways to synchronize resources:

The state of each of these objects is either signaled or not-signaled. A signaled state indicates a resource is available for a process or thread to use it. A not-signaled state indicates the resource is in use.

The routines described in the following sections manage the creation, initialization, and termination of resource sharing mechanisms. Some of them change the state to signaled from not-signaled. The routines WaitForSingleObject and WaitForMultipleObjects also change the signal status of an object. For information on these functions, see Synchronizing Threads.

This section also contains information about:

For resources about coordinating and synchronizing Win32 threads, see Other Sources of Information.

Critical Sections

Before you can synchronize threads with a critical section, you must initialize it by calling InitializeCriticalSection. Call EnterCriticalSection when beginning to process the global variable, and LeaveCriticalSection when the application is finished with it. Both EnterCriticalSection and LeaveCriticalSection can be called several times within an application. For Multithreaded Visual Fortran Samples that use Critical Sections, see PEEKAPP.F90 or PEEKAPP3.F90.

Mutexes

CreateMutex creates a mutex object. It returns an error if the mutex already exists (one by the same name was created by another process or thread). Call GetLastError after calling CreateMutex to look for the error status ERROR_ALREADY_EXISTS. You can also use the OpenMutex function to determine whether or not a named mutex object exists. When called, OpenMutex returns the object's handle if it exists, or null if a mutex with the specified name is not found. Using OpenMutex does not change a mutex object to a signaled state; this is accomplished by one of the wait routines described in Synchronizing Threads.

ReleaseMutex changes a mutex from the not-signaled state to the signaled state. This function only has an effect if the thread calling it also owns the mutex. When the mutex is in a signaled state, any thread waiting for it can acquire it and begin executing.

Semaphores

Functions for handling semaphores are nearly identical to functions that manage mutexes. CreateSemaphore creates a semaphore, specifying an initial as well as a maximum count for the number of threads that can access the resource. OpenSemaphore, like OpenMutex, returns the handle of the named semaphore object, if it exists. The handle can then be used in any function that requires it (such as one of the wait functions described in Synchronizing Threads). Calling OpenSemaphore does not reduce a resource's available count; this is accomplished by the function waiting for the resource.

Use ReleaseSemaphore to increase the available count for a resource by a specified amount. You can call this function when the thread is finished with the resource. Another possible use is to call CreateSemaphore, specifying an initial count of zero to protect the resource from access during an initialization process. When the application has finished its initialization, call ReleaseSemaphore to increase the resource's count to its maximum.

Events

Event objects can trigger execution of other threads. You can use events if one thread provides data to several other threads. An event object is created by the CreateEvent function. The creating thread specifies the initial state of the object and whether it is a manual-reset or auto-reset event. A manual-reset event is one whose state remains signaled until it is explicitly reset by a call to ResetEvent. An auto-reset event is automatically reset by the system when a single waiting thread is released.

Use either SetEvent or PulseEvent to set an event object's state to signaled. OpenEvent returns a handle to the event, which can be used in other function calls. ReleaseEvent releases ownership of the event.

Memory Use and Thread Stacks

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. All the variables declared in a multithread routine are by default static and shared among the threads. If you do not want one thread to overwrite a variable used by another, you can do one of the following:

Variables declared as automatic are placed on the stack, which is part of the thread context saved with the thread. Automatic variables within procedures are discarded when the procedure completes execution.

I/O Operations

Although files and units are shared between threads, you may not need to coordinate the use of these shared resources by threads. Fortran treats each input/output statement as an atomic operation. If two separate threads try to write to the same unit and one thread's output operation has started, the operation will complete before the other thread's output operation can begin.

The operating system does not impose an ordering on threads' access to units or files. For example, the non-determinate nature of multithread applications can cause records in a sequential file to be written in a different order on each execution of the application as each thread writes to the file. Direct access files might be a better choice than sequential files in such a case. If you cannot use direct access files, use mutexes to impose an ordering constraint on input or output of sequential files.

Certain restrictions apply to blocking functions for input procedures in QuickWin programs. For details on these restrictions, see Using QuickWin.