Hello Przemek [Xbase++]
Here is the complete implementation detail how various threads coordinate with each other : Controlling threads using signals ------------------------------ There is a possibility for coordinating threads which does not require a thread to terminate before another thread resumes. However, this requires the usage of an object of the Signal class. The Signal object must be visible in two threads at the same time. With the aid of a Signal object, one thread can tell one or more other threads to leave their wait state and to resume program execution: Thread A Thread B running running // simultaneous execution | | // of program code | oSignal:wait() // thread B stops | | wait state // thread A executes code oSignal:signal() | running // thread B resumes | | Whenever a thread executes the :wait() method of a Signal object, it enters a wait state and stops program execution. The thread leaves its wait state only after another thread calls the :signal() method of the same Signal object. In this way a communication between threads is realized. One thread tells other threads to leave their wait state and to resume program execution. Mutual exclusion of threads -------------------------- As long as multiple threads execute different program code at the same time, the coordination of threads is possible using wait states as achieved with :synchronize() or ThreadWait(). However, wait states are not possible if the same program code is running simultaneously in multiple threads. A common example for this situation is adding/deleting elements to/from a globally visible array: PUBLIC aQueue := {} FOR i:=1 TO 10000 // This loop cannot Add( "Test" ) // be executed in Del() // multiple threads NEXT ********************** FUNCTION Add( xValue ) RETURN AAdd( aQueue, xValue ) ************** FUNCTION Del() LOCAL xValue IF Len(aQueue) > 1 xValue := aQueue[1] ADel ( aQueue, 1 ) ASize( aQueue, Len(aQueue)-1 ) ENDIF RETURN xValue In this example, the array aQueue is used to temporarily store arbitrary values. The values are retrieved from the array according to the FIFO principle (First In First Out). Function Add() adds an element to the end of the array, while function Del() reads the first element and shrinks the array by one element (note: this kind of data management is called a Queue). When the functions Add() and Del() are executed in different threads, the PUBLIC array aQueue is accessed simultaneously by multiple threads. This leads to a critical situation in function Del() when the array has only one element. In this case, a runtime error can occur: Thread A Thread B LOCAL xValue IF Len(aQueue) > 1 Thread is Thread executes function completely interrupted by the operating system LOCAL xValue IF Len(aQueue) > 1 xValue := aQueue[1] ADel ( aQueue, 1 ) ASize( aQueue, Len(aQueue)-1 ) ENDIF RETURN xValue Thread resumes xValue := aQueue[1] Runtime error: Meanwhile, the array is empty The operating system can interrupt a thread at any time in order to give another thread access to the CPU. If threads A and B execute function Del() at the same time, it is possible that thread A is interrupted immediately after the IF statement. Thread B may then run the function to completion before thread A is scheduled again for program execution. In this case, a runtime error can occur because the function Del() is not completely executed in one thread before another thread executes the same function. The example function Del() represents those situations in multi-threading which require muliple operations to be executed in one particular thread before another thread may execute the same operations. This can be resolved when thread B is stopped while thread A executes the Del() function. Only after thread A has run this function to completion may thread B begin with executing the same function. Such a situation is called "mutual exclusion" because one thread excludes all other threads from executing the same code at the same time. Mutual exclusion of threads is achieved in Xbase++ not on the PROCEDURE/FUNCTION level but with the aid of SYNC methods. The SYNC attribute for methods guarantees that the method code is executed by only one thread at any time. However, this is restricted to one and the same object. If one object is visible in multiple threads and the method is called simultaneously with that object, the execution of the method is serialized between the threads. In contrast, if two objects of the same class are used in two threads and the same method is called with both objects, the program code of the method runs parallel in both threads. As a result, mutual exclusion is only possible if two threads attempt to execute the same method with the same object. The object must be an instance of a user-defined class that implements SYNC methods. A SYNC method is executed entirely in one thread. All other threads are automatically stopped when they attempt to execute the same method with the same object. The example with the beforementioned PUBLIC array aQueue must be programmed as a user-defined class in order to safely access the array from multiple threads: PUBLIC oQueue := Queue():new() FOR i:=1 TO 10000 // This loop can run oQueue:add( "Test" ) // simultaneously in oQueue:del() // multiple threads NEXT *********** CLASS Queue // Class for managing PROTECTED: // a queue VAR aQueue EXPORTED: INLINE METHOD init ::aQueue := {} // Initialize array RETURN self SYNC METHOD add, del // Synchronized methods ENDCLASS METHOD Queue:add( xValue ) // Add an element RETURN AAdd( ::aQueue, xValue ) METHOD Queue:del // Remove first element LOCAL xValue // and shrink array IF Len(::aQueue) > 1 xValue := aQueue[1] ADel ( ::aQueue, 1 ) ASize( ::aQueue, Len(::aQueue)-1 ) ENDIF RETURN xValue In this example, the Queue class is used for managing a dynamic array that may be accessed and changed from multiple threads simultaneously. The array is referenced in the instance variable :aQueue and it is accessed within the SYNC methods :add() and :del() . The Queue object which contains the array is globally visible. The execution of the :del() method is automatically serialized between multiple threads: Thread A Thread B | | oQueue:del() | | oQueue:del() <...> ADel( ::aQueue, 1 ) thread is stopped <...> RETURN xValue thread resumes | <...> | ADel( ::aQueue, 1 ) | <...> | RETURN xValue | | When thread B wants to execute the :del() method while this method is executed by thread A, thread B is stopped because it wants to execute the method with the same Queue object. Therefore, SYNC methods are used whenever multiple operations must be guaranteed to be executed in one thread before another thread executes the same operations. A SYNC method can be executed with the same object only in one thread at any time (Note: if a class method is declared with the SYNC attribute, its execution is serialized for all objects of the class). Signal() - Class function of the Signal class. ------------------------------------------ Return The function Signal() returns the class object of the Signal class. Description Signal objects are used to control program flow within one or more threads from one other thread. To achieve this, it is necessary that one and the same Signal object is visible in at least two threads. Therefore, Signal objects can only be used in conjunction with Thread objects. In other words, at least one additional Thread object must exist in an application for the Signal class to become useful (note: the Main procedure is executed by an implicit Thread object). There are two methods in the Signal class: :signal() and :wait() . The method :signal() triggers a signal, while the :wait() method causes a thread to enter a wait state. If a thread executes the :wait() method, it waits for the signal and suspends program execution. The thread resumes as soon as a second thread executes the :signal() method with the same Signal object. With the aid of Signal objects it becomes possible for the current thread to control program flow in other threads. Each thread which executes the :wait() method enters a wait state until another thread executes the :signal() method. Caution: Multiple Signal objects can be used to control program execution in multiple threads. Since a Signal object limits program control in threads to a wait state, so-called "dead lock" situations can occur when multiple Signal objects are used. A dead lock is reached when thread A waits for a signal from thread B, while, at the same time, thread B waits for a signal from thread A. Class methods :new() Creates an instance of the Signal class. Instance variables :cargo Instance variable for ad-hoc use. Attribute: EXPORTED Datatype: All data types Methods :signal() --> self Triggers a signal for other threads. :wait( [<nTimeOut>] ) --> lSignalled Waits until another thread triggers the signal. Example - 1 // Simple screen saver // A simple screen saver for text mode applications is programmed // in this example. The code for the screen saver runs in // a separate thread which waits to be signalled for a maximum of // 5 seconds. If no signal is triggered, the screen saver is // activated. The thread is signalled from the Main procedure // each time an event (keyboard, mouse) is taken from the // event queue. #include "AppEvent.ch" PROCEDURE Main LOCAL nEvent, oThread, oSignal ? "Press a key. ESC quits" oThread := Thread():new() oSignal := Signal():new() oThread:start( "ScreenSaver", oSignal, 5 ) DO WHILE nEvent <> xbeK_ESC nEvent := AppEvent(,,,0) oSignal:signal() ? "Event =", nEvent ENDDO RETURN ** Procedure runs in separate thread PROCEDURE ScreenSaver( oSignal, nSeconds ) LOCAL cScreen, cTemp DO WHILE .T. IF .NOT. oSignal:wait( nSeconds * 100 ) // This code is executed if no signal is triggered // for more than <nSeconds> seconds cScreen := SaveScreen( 0,0, MaxRow(), MaxCol() ) cTemp := SubStr( cScreen, 2 ) + Left( cScreen, 1 ) // Change screen until thread is signalled DO WHILE .NOT. oSignal:wait(10) cTemp := SubStr( cTemp, 3 ) + Left( cTemp, 2 ) RestScreen( 0,0, MaxRow(), MaxCol(), cTemp ) ENDDO RestScreen( 0,0, MaxRow(), MaxCol(), cScreen ) cScreen := ; cTemp := NIL ENDIF ENDDO RETURN Example - 2 // Controlling Append in two threads using signals // A new database is created in this example and records from // an existing database are appended. An array is used as a // read/write buffer to transfer data from the existing to the // new database. Reading and writing data runs simultaneously // in two threads. Two Signal objects coordinate which thread // may read and which one may write. A third signal causes // the second thread to terminate. PROCEDURE Main LOCAL oReadDone, oWriteDone, oDoExit, oThread, aValues USE LargeDB DbCreate( "Test", DbStruct() ) oThread := Thread():new() aValues := Array( FCount() ) oReadDone := Signal():new() oWriteDone := Signal():new() oDoExit := Signal():new() oThread:start( "AppendTo", "Test" , aValues , ; oReadDone, oWriteDone, oDoExit ) oWriteDone:wait() // Wait until 2nd database // is open DO WHILE .T. // Transfer record to array Aeval( aValues, {|x,i| x:=FieldGet(i) },,, .T. ) IF ! Eof() oReadDone:signal() // 1st signal: record is read SKIP ELSE oDoExit:signal() // 2nd signal: terminate thread #2 EXIT ENDIF oWriteDone:wait() // Wait until data is written ENDDO // to new database oThread:synchronize(0) USE RETURN ** Procedure runs in separate thread PROCEDURE AppendTo( cDbFile, aValues, ; oReadDone, oWriteDone, oDoExit ) USE (cDbFile) EXCLUSIVE oWriteDone:signal() // Signal: database is open DO WHILE .T. IF oReadDone:wait(10) // Check if record is read DbAppend() // Write data AEval( aValues, {|x,i| FieldPut(i,x) } ) oWriteDone:signal() // Signal: Data is written ELSEIF oDoExit:wait(10) // Got an Exit signal? EXIT ENDIF ENDDO USE RETURN //-------------------------------------------------// More to follow. Regards Pritpal Bedi -- View this message in context: http://www.nabble.com/MT-and-Thread-Control---Signals-tp19523545p19523545.html Sent from the Harbour - Dev mailing list archive at Nabble.com. _______________________________________________ Harbour mailing list Harbour@harbour-project.org http://lists.harbour-project.org/mailman/listinfo/harbour