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.
