Super Prev Next

Creating multithread programs

Multithreading is an operating system feature that is supported by Native XDS-x86 for Windows. This chapter describes the creation of multithread programs using Native XDS-x86 and discusses restrictions that multithreading imposes on these programs.


Super Prev Next

Overview

A thread is the smallest unit of execution within a process. Each thread has its own stack and registers; other resources of a process, such as global data, files, pipes, etc., are shared by all its threads. Any process has at least one primary thread, also called the main thread or thread 1. It is started by the operating system when the process is created and may itself create other threads when necessary. When the main thread terminates, the whole process terminates.

All threads of a process are executed concurrently in the sense that the operating system allocates small amounts of CPU time (time slices) to threads according to their priorities. This process is called scheduling. Of course, on a multiprocessor system true concurrency is possible, so the application may execute faster.

The application has no way to precisely control scheduling of its threads. It may, however, suspend threads, block them on semaphores, and modify their priorities.


Super Prev Next

The multithread library

Native XDS-x86 includes two implementations of the runtime library: singlethread and multithread:

LIBXDS.LIB singlethread for static linking
LIBXDSMT.LIB multithread for static linking
XDS230I.LIB singlethread for dynamic linking
XDS230MI.LIB mulitthread for dynamic linking

A multithread environment imposes certain restrictions on the runtime library. Many library procedures share data and other resources. The access to these resources must be serialized, i.e. not more than one thread at a time may use the resource, provided the resource is modified during its usage.

Consider a process that has two threads, A and B and the thread A dynamically allocates a variable using the procedure NEW. Now, the operating system schedules the thread B for execution before the completion of the procedure NEW in the thread A, and the thread B also issues a call to NEW. Because the thread A might have left the heap in an intermediate state, the second call of NEW may fail or corrupt the heap. Heap corruption may as well happen when the thread A resumes execution.

In the multithread library, processing of the second call will be suspended until completion of the first call, eliminating the possibility of heap corruption.

The multithread implementation of the XDS runtime library uses semaphores to protect resources accessed by a thread from being corrupted by other threads. So operations involving input/output channels, memory allocation and deallocation, etc. are serialized by the runtime library.

The library procedures that do not use global data and do not access resources are reentrant, i.e. may be safely called by more than one thread at a time while not blocking any of the calling threads. Note, however, that procedures with VAR parameters and procedures that accept parameters that contain pointers are not truly reentrant. If you access the same piece of memory from multiple threads simultaneously, the results are unpredictable, so you should provide your own serialization.

Let the thread A call Strings.Append("Word",s) to append "Word" to the string kept in the variable s. If the thread A had not yet put the trailing 0C when the thread B was scheduled for execution, the call to Strings.Length(s) from the thread B may return incorrect result if the unused rest of s is not filled with zeroes. A semaphore must have been used to protect s from being accessed concurrently when the state of s is not known.

The configuration file and the template file included in your XDS package allow you to select the version of the runtime library to be linked with your program. Turn the MULTITHREAD option ON in the project file or in the command line to link with the multithread library. By default, this option is set OFF and the singlethread library is linked in.


Super Prev Next

Creating and destroying threads

In the multithread library, the ISO Modula-2 standard module Processes is implemented over threads. The procedure Processes.Create (Processes.Start) not just creates a thread but also performs actions required to ensure correct processing of runtime library calls and exception handling in that thread. If you use the multithread library, you must start all threads that use the runtime library and exception handling via one of these two procedures.

The procedure Processes.Create (Processes.Start) accepts the new thread’s stack size in bytes through the extraSpace parameter. The procUrg parameter is the initial priority of that thread and is interpreted differently depending on the target operating system:

Windows NT/95

procUrg Priority value
-3 THREAD_PRIORITY_IDLE
-2 THREAD_PRIORITY_LOWEST
-1 THREAD_PRIORITY_BELOW_NORMAL
0 THREAD_PRIORITY_NORMAL
1 THREAD_PRIORITY_ABOVE_NORMAL
2 THREAD_PRIORITY_HIGHEST
3 THREAD_PRIORITY_TIME_CRITICAL

As can be seen, the procUrg value of zero means regular priority (the priority that the main thread has after it is started) on all operating systems. If you do not need to alter thread priority, you may safely pass zero to procUrg regardless of the target operating system.

The Processes.StopMe procedure effectively terminates the calling thread. Another way for a thread to terminate normally is to RETURN from the thread’s body (the procedure passed as the procBody parameter to Processes.Create or Processes.Start).

Threads that do not use the runtime library and exception handling may be started using the target operating system API call: OS2.DosCreateThread or Windows.CreateThread. In this case, the procedure comprising the body of the thread must be declared with the ["SysCall"] or the ["StdCall"] direct language specifier respectively. The Processes.StopMe procedure should not be used to terminate such a thread.


Super Prev Next

Operating system specifics

The module Processes provides a portable interface to the multithreading facilities of the target operating system. Unfortunately, this modules, as defined in ISO10514 is too general and restricted in comparison with the Win32 API. The XDS library module Win32Processes implement Windows-specific extensions to Processes, overviewed in the following subsections. Refer to the respective definition modules for more information.


Super Prev Next

Thread handles

The procedure GetThreadHandle returns the handle of the thread associated with the given process id. This handle may be then used in all thread-related API calls; the only exception is that a thread created using Processes.Create or Processes.Start must be stopped using Processes.StopMe.


Super Prev Next

Event sources

The ISO Modula-2 standard does not specify what is an event source, declaring this feature implementation dependent. The procedure MakeSource can be used to create event sources from the undelaying operating system resources, such as semaphores. These sources may be then used in calls to Processes.Attach, Processes.Detach, Processes.IsAttached, and Processes.Handler.

See the correspondent definition module for more information.


Super Prev Next

Volatile variables

The optimizations performed by the Native XDS-x86 compiler may cause some variables to be temporarily placed on registers, and to receive values different from those specified in the source code and at different point of execution. These optimizations should be disabled for variables that are referenced by more than one thread.

If the VOLATILE option was switched ON during compilation of a variable definition, the compiler assumes that references to that variable may have side effects or that the value of the variable may change in a way that can not be determined at compile time. The optimizer does not eliminate any operation involving that variable, and changes to the value of the variable are stored immediately.

    <* PUSH *>
    <* VOLATILE+ *>
    VAR
      common: CARDINAL;
    <* POP *>


Super Prev Next

Exception handling


Super Prev Next

Modula-2 exceptions

All threads share a single instance of each exception source. As a result, a thread will be blocked when an exception is raised in it if another thread is handling an exception with the same source, until it leaves the exceptional execution state.


Super Prev Next

System exceptions

A thread started via an API call is responsible for intercepting and handling system exceptions, such as arithmetic overflow or access violation.