Statement SYNC Foundation
Declares a method which is synchronized across any number of threads
SYNC [CLASS] METHOD <MethodName>
When an object is visible in multiple threads at the same time, the SYNC attribute makes sure that the program code of a method is executed entirely in one thread before another SYNC method of the same object can be executed in another thread. SYNC methods are automatically synchronized when executed by different threads. Therefore, when different SYNC methods are called for the same object (self) from multiple threads, they are executed in sequential order without being interrupted. Only one SYNC method can be executed using the same object (self) at any given point in time.
In multi-threaded programs, the SYNC attribute is necessary when a method performs multiple operations to achieve a consistent state of an object (self). This is the case, for example, when multiple instance variables are changed within a method and the object's consistency depends on all changes to be completed before the method is executed in another thread. The SYNC attribute guarantees that the program code of a method will run to completion within a single thread. No other thread can execute a SYNC method with the same object before the method has returned.
SYNC methods and Signals
There is one exception to the above stated rule. If thread A has suspended program execution of a SYNC method due to waiting for a signal, another SYNC method of the same object can be invoked in thread B. The suspended thread A will resume with method execution after being signalled only when the second SYNC method has run to completion in thread B (see the second example below).
// A class for managing a queue is programmed in this example.
// The queue is realized using an array that can be accessed
// from multiple threads via the methods :put() and :get().
// Four threads are started in the Main() procedure which are
// accessing the queue. If either method :put() or :get() is
// declared without the SYNC attribute, a runtime error will
// occur sooner or later because the Queue object will fall
// into an inconsistent state.
CLASS Queue
PROTECTED:
VAR aQueue
EXPORTED:
VAR nCount READONLY
INLINE METHOD init
::aQueue := {}
::nCount := 0
RETURN self
SYNC METHOD put, get
ENDCLASS
METHOD Queue:put( xValue )
AAdd( ::aQueue, xValue )
::nCount ++
RETURN self
METHOD Queue:get
LOCAL xReturn
IF ::nCount > 0
xReturn := ::aQueue[1]
::nCount --
ADel ( ::aQueue, 1 )
ASize( ::aQueue, ::nCount )
ENDIF
RETURN xReturn
** Code to test a Queue object
PROCEDURE Main
LOCAL i, aT[4], oQueue := Queue():new()
CLS
FOR i:=1 TO 4
aT[i] := Thread():new() // Create threads
aT[i]:start( "Test", oQueue ) // and start them
NEXT
FOR i:=1 TO 40000 // Write values
oQueue:put(i) // to queue
NEXT
ThreadWaitAll( aT ) // Wait until all threads
RETURN // have terminated
** Code runs simultaneously in 4 threads
PROCEDURE Test( oQueue )
LOCAL i, nRow, nCol
nRow := ThreadID() + 10
nCol := 0
// Read values
FOR i:=1 TO 10000 // from queue
DispOutAt( nRow, nCol+20, oQueue:nCount )
DispOutAt( nRow, nCol+40, oQueue:get() )
NEXT
RETURN
// The example demonstrates the program flow of two SYNC methods
// when one SYNC method is suspended while waiting for a signal.
PROCEDURE Main
LOCAL oThreadA := MyThread():new()
LOCAL oThreadB := Thread():new()
oThreadA:start()
oThreadB:start( {|| oThreadA:signal() } )
ThreadWaitAll( { oThreadA, oThreadB } )
AEval( oThreadA:aHistory, {|a| QOut( a[1], a[2] ) } )
RETURN
CLASS MyThread FROM Thread
PROTECTED:
METHOD execute
EXPORTED:
VAR aHistory
VAR lTerminate
VAR oSyncSignal
SYNC METHOD add, signal
INLINE METHOD init
::Thread:init()
::aHistory := {}
::lTerminate := .F.
::oSyncSignal := Signal():new()
RETURN
ENDCLASS
METHOD MyThread:execute
Aadd( ::aHistory, { ThreadID(), "starting" } )
DO WHILE .NOT. ::lTerminate
::add()
ENDDO
Aadd( ::aHistory, { ThreadID(), "terminating" } )
::Quit()
RETURN self
METHOD MyThread:add
AAdd( ::aHistory, { ThreadId(), "suspended" } )
::oSyncSignal:Wait(0)
AAdd( ::aHistory, { ThreadId(), "signalled" } )
RETURN self
METHOD MyThread:signal
AAdd( ::aHistory, { ThreadId(), "before signal" } )
::oSyncSignal:signal()
AAdd( ::aHistory, { ThreadId(), "after signal" } )
::lTerminate := .T.
AAdd( ::aHistory, { ThreadId(), "sleep 2 seconds" } )
Sleep(200)
AAdd( ::aHistory, { ThreadId(), "terminating"} )
RETURN self
If you see anything in the documentation that is not correct, does not match your experience with the particular feature or requires further clarification, please use this form to report a documentation issue.