User Tools

Site Tools


thread_model_faq.htm
Navigation:  How To's and Troubleshooting > Thread Model FAQ >====== Thread Model FAQ ====== Previous pageReturn to chapter overviewNext page

The following topic provides detailed information regarding thread model terminology and techniques used in Clarion 6

Contents:

In the new Clarion thread implementation, is there now a requirement for the developer to explicitly lock an object/variable?

How does the locking of an object or variable occur, does it happen automatically whenever it is accessed?

Now that multiple threads are active concurrently, how can I safely share access to non-threaded static data (global variables) amongst threads?

What are Critical Sections and what do I need to know how to use them?

How can Clarion programs access Critical Sections?

What are Mutexes?

How does the new thread model impact working with FILE and VIEW structures?

How does the new thread model affect CLASSes?

What kind of problems might I run into writing my own code?

What has the new thread model changed that I must understand?

Has the syntax of data declarations changed?

How can I reference a threaded variable from another thread?

Is the RTL now re-entrant?

In the new Clarion thread implementation, is there now a requirement for the developer to explicitly lock an object/variable?

Both current and prior Clarion versions do nothing to synchronize access to static data (global non-threaded objects). Therefore the new thread implementation breaks nothing. The decision on how to access global non-threaded data is still completely for the programmer to decide.

Practice has shown that there are problems with the automatic swapping schema of threaded data. Therefore in the new thread model data is handled completely differently. The main difference is that in the new implementation every instance of a threaded variable has its own location/address.

Note that without explicit synchronization of active threads, a thread can be suspended by the Windows O/S and another switched to at any time, for example a thread switch could occur in the middle of an assignment such as A = B. The problem that this can highlight comes about if you have more than one thread writing to the same non-threaded variable, which is in itself a questionable coding technique.

How does the locking of an object or variable occur, does it happen automatically whenever it is accessed?

Threaded variables or objects can be accessed without any locking: local data is created on the thread stack and can't be directly accessed (by name) from other threads; access to threaded data is controlled by the RTL via code generated by the compiler. So, possible conflicts can only occur with static data; i.e. _global_ variables declared without the thread attribute.

Now that multiple threads are active concurrently, how can I safely share access to non-threaded static data (global variables) amongst threads?

Just use a synchronization object such as a critical section or mutex. Named mutexes, semaphores, pipes, events are targeted for inter-process synchronization.

What are Critical Sections and what do I need to know how to use them?

Critical Sections allow us to enforce mutual exclusion. Critical section objects provide synchronization similar to that provided by mutex objects, except that critical section objects can be used only by the threads of a single process. Event, mutex, and semaphore objects can also be used in a single-process application, but critical section objects provide a slightly faster, more efficient mechanism for mutual-exclusion synchronization. The important property that critical sections have is that only one thread may have ownership at any one time. If a thread tries to enter a critical section when another thread is already inside the critical section, then it will be suspended, and only resumed when the other thread has left the critical section. This provides us with the required mutual exclusion around a shared resource. More than one thread can be suspended waiting for ownership at one time, so critical sections can be used for synchronization between more than two threads

How can Clarion programs access Critical Sections?

Clarion programs can use a CRITICAL_SECTION (and other synchronization objects) and can do so without diving into their complexity. This can be coded for critical sections as follows:

MAP

MODULE() NewCriticalSection PROCEDURE(),*IcriticalSection END END   Sync &ICriticalSection !The critical section used to synchronize access to data CODE

Sync &= NewCriticalSection() ! Create the critical section object

Sync.Wait() ! lock the critical section

… code to access shared resource

Sync.Release() ! unlock the critical section

Sync.Kill() ! Delete the critical section

What are Mutexes?

Mutexes are another kind of synchronization object. Their goal is the same as for critical sections: provide mutual exclusive access to some shared resource. For example, to prevent two threads from writing to shared memory (global data) at the same time, each thread waits for ownership of a mutex object before executing the code that accesses the shared resource.

There are 2 major differences between critical sections and mutexes:

Critical sections can only be used for synchronization of threads owned by the same process. Mutexes can be used for synchronization of threads that can belong to different processes. If a process creates a mutex with some name and another (or the same) process has created a mutex with that name already, the system does not create a new mutex. It returns another handle to the existing mutex instead and sets error code to 183 (ERROR_ALREADY_EXISTS). Unnamed mutexes are local for the process that created them. Such mutexes can only be used for synchronization of that process's threads.

When we use a critical section for synchronization and the requested critical section is already “occupied”, the thread that tries to enter it is suspended until the critical section is available, or the method call that “tries” to enter the critical section can return immediately. The difference is that TryEnterCriticalSection returns immediately, regardless of whether it obtained ownership of the critical section, while EnterCriticalSection blocks until the thread can take ownership of the critical section. With a critical section there is no ability to specify a time limit on the wait. The IMutex.Lock() method behaves in the same manner: the thread that executed this method is suspended until it can gain ownership of the mutex. But it's possible to limit the waiting time by using the IMutex.Wait method with a parameter equal to the number of milliseconds to wait. If the mutex is not released within the given time period, the thread is resumed and the method returns 1. If the mutex is not locked by another thread, or it is released within the given “wait” period, the thread continues its execution and the method returns 0. Supplying a time parameter equal to 0FFFFFFFFh (-1) means an infinite wait.

How does the new thread model impact working with FILE and VIEW structures?

In the new thread model any FILE declared in the procedure or routine local scope is treated as threaded regardless of the presence of the THREAD attribute in its declaration.

VIEWs have no THREAD attribute by syntax, but VIEWs declared in the procedure or routine local scope are treated as threaded. A VIEW declared in the global or module scope is treated as threaded if at least one joined FILE is threaded.

How does the new thread model affect CLASSes ?

Threaded CLASSes are fully supported now. The constructors and destructors for threaded classes are called for every thread now. Every new thread gets new instances of CLASSes and variables declared at the global level with the THREAD attribute. The RTL calls constructors for the threaded classes when the thread is started and the destructors when the thread is ended. In previous Clarion versions they were called only when the main thread started and ended.

It follows that in previous versions of Clarion, to handle thread specific information within classes there was a need to have arrays or queues. See for example, Info field of the FileManager class. The only instance of FileManager is responsible for handling all the instances of a FILE. As a result it must retrieve information about current thread context (FileManager.SetThread) on every call to any other method. Now an instance of a threaded class can carry thread related data without involving external structures that require synchronized access to them in the case of preemptive thread switching.

Use of threaded classes can make the whole class hierarchy more complex but

orthogonal. This is because all instances of the same class can share some information. For example, the relation between two tables is independent from any particular class instance: keys, fields and possibly expressions, are the same. So, one class can be split into two or more classes: one carries thread specific information and behavior, and others thread independent data and behavior.

What kind of problems might I run into writing my own code?

You need to remember that access to static variables (a global variable that doesn't have the THREAD attribute) is not synchronized; this means that the value of a variable can be changed at any time from other concurrently running threads. As a result, any condition involving static variables can be changed just after its evaluation, i.e.

IF A = 0

! Value of A can be changed at this point from another thread

DoSomething(A)

END

The solution to this type of problem is to use the THREAD attribute on declarations, or use synchronization if you actually intended for the variable to be shared across threads.

You also need to know that the error functions (i.e., ERRORCODE( )) are now thread dependent. This means that an error raised on one thread are not detectable on other threads. This may effect some of your code if you are using error states to transmit information between threads.

What has the new thread model changed that I must understand?

In previous Clarion versions all instances of a threaded variable shared the same location in the program. Therefore, the result of a reference assignment with a threaded variable, or the field of a threaded structure as a source is valid for all threads, for example:

F FILE,…,THREAD

END

A ANY

CODE

A &= WHAT (F.Record, 1)

In all threads A points to the first field of the file's record. In the new model every instance of a threaded variable has its own address. Therefore, the result of a reference assignment as given above is valid only in the thread where it has been executed.

There are a number of structures in ABC which use a field with thread numbers to distinguish data for a particular thread. Examples are FileThreadQueue or StatusQ in ABFILE.CLW. The fact that every instance of a threaded variable now has its own address required that the schema for initialization of most FILE-related ABC classes were in need of some changes. (We have already done the changes and will utilize same as a teaching tool).

On the other hand, global reference variables set to particular instances of threaded variables can be useful for passing information across threads: for example one thread can change an instance belonging to another thread.

Has the syntax of data declarations changed?

No, no change in syntax. However, because the address of the instance for the starting thread is only known at compile time the compiler must generate code to calculate the address of a required instance. There are two possibilities here: the variable is External and imported from another DLL, or it is located in the same executable.

So, one change is that the EXTERNAL and THREAD attributes in data declarations are not mutually exclusive now. The THREAD attribute is the only way to inform the compiler that it must generate the code to get the correct instance.

How can I reference a threaded variable from another thread?

The new INSTANCE function: INSTANCE(variable,threadno) returns the address for the instance of the variable allocated for the specified thread referenced by threadno, provided the referenced thread is active.

This can be used in code as follows:

addressvar = INSTANCE(SalesFile,THREAD())

!return address of SalesFile entity on the active thread

INSTANCE is also valuable when you need the thread independent ID of the variable.

addressvar = INSTANCE(GLO:LoginID, 0)

!get the thread independent ID of a global threaded var

Is the RTL now re-entrant?

The Clarion RTL has per-process and per-thread initialization flags in the header. So, every process will get its own copy of the RTL and Windows does not load the same DLL into process memory space a second time (unless it has been unloaded manually). So Yes, it's possible to consider the RTL as re-entrant.

thread_model_faq.htm.txt · Last modified: 2021/04/15 15:57 by 127.0.0.1