In applications that use more than one user program thread, the operating system dynamically switches execution of the threads. From instant to instant, there is no guarantee that any particular thread will continue execution. A thread can be swapped out and another thread swapped in at any moment, even in the middle of an instruction.
When two or more user program threads access the same data, they may interact in an unexpected way. For example, if two user threads both attempt to increment the same GPL variable, an intermittent bug may occur. If both threads execute the statement: a = a + 1, the following (Table 19-42) may happen, assuming a starts at value of 0:
| Thread Switching | Thread Action |
|---|---|
|
Thread 1 is running. |
Thread 1 reads the value of a. It reads the value 0. |
|
Thread 2 swaps in. |
Thread 2 reads the value of a. It reads the value 0. |
|
Thread 2 continues. |
Thread 2 adds 1 to its value and writes it to a. |
|
Thread 1 resumes. |
Thread 1 adds 1 to the value 0 it read previously, and writes it to a. |
Even though both threads intended to add 1 to a, the final value of a is 1 instead of the expected value of 2.
When an operation is thread-safe it means that it produces the same results regardless of whether a single thread or multiple threads are performing it.
Numeric and Boolean data reading is always thread-safe. All numeric data types may be read, regardless of how the data is being written. You will always get one of the values that someone has written. You can also read numeric data from statically allocated arrays or objects.
Simple writing of numeric data is also thread-safe. If multiple threads write the same variable, the result will always be one of the values written. If only one thread is writing a numeric variable, there is no need to interlock the access with threads that are reading.
Operations that first read and then write a numeric variable are not thread-safe, as illustrated by the example in the previous section. It is always possible for another thread to write the data value while the original thread is modifying it.
Groupings of numeric values (arrays and objects with multiple embedded values) are not thread-safe. For example, if one thread changes the X and Y values of a location, a second thread may see a transient condition where only the X or Y is changed.
String data is not thread safe. If one thread is reading a string value while another thread is writing it, the reader may see a mixture of the old data and the new data. Simple string assignment is thread-safe since the final value will be one of the values written. However most string methods that modify the string values are not thread-safe.
Objects are generally not thread-safe and there is no interlocking among the object fields. However individual numeric fields within an object are thread-safe.
Dynamic arrays are not thread-safe, even if they contain numeric data. These are arrays whose sizes are altered using a ReDim statement to change their size during execution. Using ReDim to change an array size while other threads are accessing the array could result in a system crash that requires rebooting.
Thread-safe interlocks may be created using the GPL Thread.TestAndSet method. This method is fully described in the GPL Dictionary section. Sample lock and unlock routines are shown below:
' Lock the semaphore. Wait until lock is obtained.
Public Sub acquire_sem(ByRef sem_var As Integer)
While Thread.TestAndSet(sem_var, 1) <> 0
Thread.Sleep(0)
End While
End Sub
' Unlock the semaphore
Public Sub release_sem(ByRef sem_var As Integer)
sem_var = 0
End Sub
This acquire_sem() routine waits indefinitely until the lock can be obtained. If desired, this routine can be enhanced to wait for a limited time and return an error or throw an exception if that time limit is exceeded.
You can use these routines to lock a thread during an unsafe data access, to guarantee that no unsafe access occurs. The example below shows how to safely interlock an add operation on a numeric array element.
Public my_lock As Integer
Public my_array(1) As Integer
Public Sub AddArray(ByVal inc As Integer)
acquire_sem(my_lock) ' Prohibit access by other threads
my_array(0) = my_array(0) + inc
release_sem(my_lock) ' Allow write access by other threads
End Sub
For numeric values, the read operation is thread-safe, so no special action is required, but for a string operation, both the read and write operations need to be interlocked. The example below shows interlocking both the read and write operation for a string variable.
Public my_lock As Integer
Public Sub AppendString(ByRef sg As String, ByVal app As String)
acquire_sem(my_lock) ' Prohibit access by other threads
sg &= app ' Modify string while locked
release_sem(my_lock) ' Allow access by other threads
End Sub
Public Function ReadString(ByRef sg As String) As String
Dim ret_string As String
acquire_sem(my_lock) ' Prohibit access by other threads
ret_string = sg ' Copy string while locked
release_sem(my_lock) ' Allow access by other threads
Return ret_string
End Function