Silverfrost Logo About Us | Contact Us
 

Threads

The .NET framework supports threads. This means that it is possible to have a program that is effectively doing several things at the same time. Multiple threads can be very useful but programs with threads are usually much more difficult to debug. Consequently they should be used sparingly.

When a program contains two or more threads its execution is analogous to the situation where multiple programs are running simultaneously. However the various threads share the same address space. On a typical single processor machine each active thread runs for a short period of time until it is swapped out to permit another thread to run. This can be useful, for example, when you want to control a GUI interface or access data over the Internet whilst a long numeric calculation is proceeding. Using threads in such circumstances can make your program much more responsive to user interaction.

Threading is also the key to exploiting the power of multi-processor machines. When a machine has several processors (typically two) several threads can literally run simultaneously - true parallel processing. This happens automatically if several processors are available.

Although it is possible to create and start a thread using calls to the .NET framework, FTN95 provides a shortcut:

SUBROUTINE CREATE_THREAD@(SUB,ICODE)
EXTERNAL SUB
INTEGER ICODE

SUB is the routine in your code that is called to start the thread. SUB is presented in the form of a subroutine that accepts one integer argument. The value that is used in the resulting call to SUB is the value of ICODE. Thus you can choose to start several threads using the same subroutine but with different values of ICODE to distinguish the cases. Here is a simple example of code to start a thread:

EXTERNAL foo
CALL CREATE_THREAD@(foo,42)
PRINT*,'Finishing main program.'
END

SUBROUTINE foo(m)
INTEGER m
PRINT*,'In foo ',m
END

In this example the two PRINT statements may execute in either order and this will typically vary from one run to another. This reflects the fact that the threads in your program run asynchronously. This program will continue to execute until all threads have terminated.

The first call to CREATE_THREAD@ in a program enables re-entrant Fortran I/O so that I/O on one thread does not cause a fault when it coincides with I/O on another thread. If you use I/O in several threads you must use different unit numbers unless you use the LOCK statement to prevent a swap before the I/O statement has finished. For example:

LOCK
PRINT*,........
END LOCK

If you use threads you must design your program in such a way that each thread can run independently. Moreover, it is important to note that a program that contain multiple threads might contain bugs that only show up intermittently (because the way in which threads interact can vary from one run to another). For this reason programs with multiple threads can be very hard to debug.

As an alternative to CREATE_THREAD@, the FTN95 routine CREATE_THREAD1@ provides for an additional argument that returns a handle for the thread. This handle can be used in calls to System.Threading.Thread methods and these enable the user to interact with a thread that is running. For example, a call can be made to System.Threading.Thread.Join in order to wait for a thread to terminate.

Here is the form of the interface for CREATE_THREAD1@:

SUBROUTINE CREATE_THREAD1@(SUB,ICODE,NEW_THREAD)
EXTERNAL::SUB
INTEGER::ICODE
OBJECT("System.Threading.Thread") NEW_THREAD

System.Threading.Thread.Join is a static method (see Calling .NET methods from Fortran) so it can be called using, for example,

ASSEMBLY_EXTERNAL(NAME="System.Threading.Thread.Join")T_JOIN
. . .
CALL T_JOIN(NEW_THREAD)

Here is an example that illustrates the use of CREATE_THREAD1@:

EXTERNAL foo
ASSEMBLY_EXTERNAL(NAME="System.Threading.Thread.Join") T_JOIN
OBJECT("System.Threading.Thread") NEW_THREAD
CALL CREATE_THREAD1@(foo,42,NEW_THREAD)
!Do some processing...
CALL T_JOIN(NEW_THREAD)
PRINT*,'Finishing main program.'
END

SUBROUTINE foo(m)
INTEGER m
PRINT*,'In foo ',m
END

In this example the output from the subroutine will appear before that from the main program.

Other thread methods are provided in addition to Join. These are described in the .NET documentation.

When using the .NET framework there are certain overheads that are incurred by multi-threaded programs. Because of this the linker DBK_LINK or DBK_LINK2 assumes by default that the code does not use multi-threading. The DBK_LINK or DBK_LINK2 command line option /MULTI_THREADED must be used to inform the linker that the code makes calls to either CREATE_THREAD@ or CREATE_THREAD1@. The same option can be employed with FTN95 when it is used with /LINK. If you do not use this option then an exception will be raised at runtime when either of these routines is called. You must also use the option /MULTI_THREADED when you create a thread directly using System.Threading.Thread methods, otherwise the resulting behaviour is undefined. This option is also required when a library is created if it contains a routine that is called on more than one thread.

If you need to start a number of threads you can used the /STACK option to reduce the default stack size per thread. /STACK is a DBK_LINK command line switch. It is also available on the FTN95 command line for use with /LINK or /LGO. The given (decimal) size of the stack refers to the number of bytes of virtual storage (i.e. address space) allocated to a single threaded program and to each thread of a multi-threaded program.

By default the stack size is set to 96 megabytes (this is over 100 million bytes; the default KIND for an INTEGER value and a REAL value each occupy 4 bytes). This large value is adequate for most (single threaded) Fortran programs even when they contain large local arrays (these are allocated on the stack). However, for multiple threads, this amount of memory is allocated for each thread and the default stack size can be unduly large. Conversely, single-threaded programs that contain exceptionally large local arrays may require a stack size that is larger than the default. You will find, however, that stack overflow exceptions are currently not well reported by the .NET framework.

 

 

Copyright © 1999-2017 Silverfrost Limited