User Tools

Site Tools


mtcoding_techniques_for_preemptive_multi_threading.htm
Navigation:  Advanced Topics > Thread Model Documentation > Multi-Threading Programming >====== Coding Techniques for Preemptive Multi-Threading ====== Previous pageReturn to chapter overviewNext page

Don't Use Static Variables

The easiest way to avoid problems is to avoid using static variables. A lot of static variables are declared as static simply because it took less time to use a global or module variable then to pass the data as a parameter. Check your static variables, and if they are used to transfer information between threads pass them as a parameter as needed.

Initialize Once/Read Many

Non Queue static variables

If you have static variables that you use to hold global information that never changes during the running of a program, then you do not need to change them. Provided the variables are initialized in the startup of the program and never changed, there is no need to worry. For example, prior to Clarion 6, when there was no SYSTEM{PROP:DataPath}, it was common to have a file name initialized based on a path stored in an INI file. This practice is still acceptable in a preemptive threading model.

Static Queues that are initialized at startup

A static queue that is initialized at startup and read many times is not safe. See the section below on Common Coding Practices that need to change for ideas on how to handle static queues.

Synchronize Access

If you are using static variables as a way to share information between threads, then you will need to synchronize thread access to these variables. The two main ways you will do this is either using a Critical Section Object or a ReaderWriterLocks Object.

If the value to be protected is a LONG (and not in a GROUP) you are probably safe in not synchronizing most thread accesses, since reads/writes happen in only one operation. If a LONG is defined in a GROUP/RECORD/QUEUE then it might not be aligned data and require more then one operation. In summary a LONG has fewer risks of corrupt half updated data then strings, but still have some risks when multiple commands are involved.

See the Synchronization Objects section below for descriptions and examples on how to use these objects.

IMPORTANT!

It is very important that you lock out other threads for as short a time as possible. Ideally, copying the data you need into thread safe variables then releasing the lock. You should not have any user input inside a piece of code that locks other threads.

To guarantee that your static variables are protected it is best to move them into a static class that has read and write methods for manipulating them. Make them PRIVATE, create Get and Set methods, and have synchronization code to protect and manipulate them.

Changes to Common Coding Practices

Using static variables to pass parameters

If you are using a static variable to pass values to a started procedure on a new thread, change the prototype of that procedure so the values it requires are passed as parameters. Both the ABC and Clarion template chains now support this feature.

Using static queues

Static queues that are shared amongst multiple threads will require you to make some code changes with regard to how they are accessed to work in a preemptive threading environment. The problem with queues is that they not only have a queue buffer, but they also have position information. So if one thread reads from the first element of the queue and another thread reads from the second element of the queue you could end up with the following situation:

Thread 1 Thread 2
GET(Queue, 1)
GET(Queue, 2)
Assign Queue Data to local var
Assign Queue Data to local var

Thread 1 gets a time slice from the OS and reads from the Queue, then the OS give thread 2 a time slice before thread 1 makes the assignment of the Queue data to a local variable. Now both thread 1 and thread 2 ends up reading the information for element 2 of the Queue. To avoid this situation you need to synchronize access to the queue's buffer. To keep the time that other threads are locked out while the queue is accessed to a minimum, you should read from the queue and assign the data from the queue's buffer into a threaded memory buffer. See the section below on ICriticalSection for a complete example showing how to do this.

Queues that are only modified at startup

A common coding technique is to set up a static queue that contains data that does not change, for example the states of the USA. This is initialized on program startup, and used throughout the application.

This technique needs to be adjusted to work in a preemptive environment for the reasons stated above. Luckily, the solution is easy – put a THREAD attribute on the QUEUE

This means that every thread will have its own instance of the QUEUE. The trouble is you need to get the data into that queue for every thread. To accomplish this you create a threaded class to do the copying for you. Below is an example class that will populate a queue for every thread.

In the following example QLock is used to make sure that two threads starting up simultaneously will not interfere with each other. The initial population of the Queue does not need modification provided it is done on the main program thread before any other threads are started. The example shown also makes use of the new INSTANCE() language statement.

GlobalQ QUEUE,THREAD

Data     STRING(10)

END

QLock &ICriticalSection

GlobalQueuePopulator CLASS,THREAD

Construct             PROCEDURE

Destruct              PROCEDURE

END

GlobalQueuePopulator.Construct PROCEDURE

BaseQ &GlobalQ

recs  UNSIGNED,AUTO

i     UNSIGNED,AUTO

CODE

IF THREAD() <;> 1

QLock.Wait()

BaseQ &amp;= INSTANCE(GlobalQ, 1)

recs = RECORDS(BaseQ)

LOOP i = 1 TO recs

GET(BaseQ, i)

GlobalQ.Data = BaseQ.Data

ADD(GlobalQ)

END

QLock.Release()

ELSE

QLock &amp;= NewCriticalSection()

END

GlobalQueuePopulator.Destruct PROCEDURE

CODE

IF THREAD() = 1

QLock.Kill()

END

WAIT() function usage

The WAIT function will wait until no other threads want the object. It then takes hold of the object until a subsequent call to Release. Other threads that call the WAIT function will wait indefinitely until the other thread releases the object.

Proper programming techniques are essential here to avoiding a “deadly embrace”, or deadlock.

For example, if you execute the following:

SyncObj1.Wait

SyncObj2.Wait

on one thread and

SyncObj2.Wait

SyncObj1.Wait

on another thread, you are risking a deadlock.

Deadlocks should not be a big issue if you make sure you have a hierarchy of synchronization objects. Ideally, you only use one object at a time, but if you must use multiple objects, always acquire a lock on the top synchronization object first, then the other one. That way, you can never get into the aforementioned scenario.

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