@q file: wthread.w@> @q% Copyright Dave Bone 1998 - 2015@> @q% /*@> @q% This Source Code Form is subject to the terms of the Mozilla Public@> @q% License, v. 2.0. If a copy of the MPL was not distributed with this@> @q% file, You can obtain one at http://mozilla.org/MPL/2.0/.@> @q% */@> @** Thread support library: native thread wrapper functions.\fbreak Supports both Microsoft's NT platform thread implementation and Pthreads. Pthreads has been tested on HP's VMS operating system, Apple's OS X platform, Ubuntu, and Sun Solaris 10 AMD workstation. See ``Pthreads Programming'' by Bradford Nichols, Dick Buttlar and Jacqueline Proulx Farrel. Easy read and well presented 2nd edition 1998. There is only one thread type: grammar requesting parallelism --- `pp' is its acromyn for parallel parse. From a parallel parsing perspective, the parsing pushdown automaton detects parallelism by the presence of the thread list within the current parse state's configuration. It now handles the all the details from launching of the threads instead of the old way that used a middleman called the control monitor ``cm'' who attended to all details related to parallel parsing and waited for the completion of the threads, and passed the results to the arbitrator functor for its ruling, and then cleaned up the accept queue.> To communicate between threads, a message protocol was developed in tandem with critical regions: I now call it an event protocol. Per thread, possession of its critical region is controlled by a mutex --- mu for short. To implement messaging a conditional variable (cv) is used having a companion variable indicating whether a event is received or not that is under mu control. The event (message) protocol was developed to remove any reliance on the operating system. I was caught by Microsoft's message queue system with its quirks, limitations, and down right tantrums. These comments are circa 1997 and probably don't hold today... but the system dependency still does so here's my take on parsing events. Simple and not too challenging intellectually. To reduce the size of the emitted cpp file, the thread implementation is outputted to |wthread.cpp| file. It's definitions etc are concatenated to the |yacco2.h| file which is used by every implementation. The following diagrams illustrates the critical region structure per thread, and the message flows acting as events between the threads. Critical regions:\fbreak Message flow:\fbreak @^ To do writeup overview of cr and msg events@> @*2 Set up the required include files. @+= #if THREAD_LIBRARY_TO_USE__ == 1 #include #include #elif THREAD_LIBRARY_TO_USE__ == 0 #include #endif @ Basic types supporting thread development. @+= typedef void* LPVOID;@/ #if THREAD_LIBRARY_TO_USE__ == 1 #define _YACCO2_CALL_TYPE //\_\_stdcall typedef HANDLE MUTEX; typedef unsigned int THREAD_NO; typedef HANDLE THREAD; typedef HANDLE COND_VAR; typedef uintptr_t THR; typedef int THR_result; typedef THR ( _YACCO2_CALL_TYPE * Type_pp_fnct_ptr) (yacco2::Parser* PP_requestor); typedef THR_result ( _YACCO2_CALL_TYPE * Type_pc_fnct_ptr)(yacco2::Parser* PP_requestor); typedef THR ( --stdcall//\_YACCO2\_CALL\_TYPE * Type_pp_fnct_ptr_voidp) (yacco2::LPVOID PP_requestor); #elif THREAD_LIBRARY_TO_USE__ == 0 #define _YACCO2_CALL_TYPE typedef pthread_mutex_t MUTEX; typedef pthread_t THREAD_NO; typedef pthread_cond_t COND_VAR; typedef void* LPVOID; typedef LPVOID THR; typedef int THR_result; typedef pthread_t THREAD; typedef THR (* Type_pp_fnct_ptr) (yacco2::Parser* PP_requestor); typedef THR (* Type_pp_fnct_ptr_voidp) (yacco2::LPVOID PP_requestor); typedef THR_result (* Type_pc_fnct_ptr)(yacco2::Parser* PP_requestor); #endif typedef std::vector yacco2_threads_to_run_type; typedef yacco2_threads_to_run_type::iterator yacco2_threads_to_run_iter_type; @ Thread's External wrapper routines.\fbreak Access to the real thread control runtime library uses wrapper routines to aid in porting to another thread library. @= extern void CREATE_MUTEX(yacco2::MUTEX& Mu); extern void LOCK_MUTEX(yacco2::MUTEX& Mu); extern void UNLOCK_MUTEX(yacco2::MUTEX& Mu); extern void LOCK_MUTEX_OF_CALLED_PARSER(yacco2::MUTEX& Mu,yacco2::Parser& parser,const char* Text); extern void UNLOCK_MUTEX_OF_CALLED_PARSER(yacco2::MUTEX& Mu,yacco2::Parser& parser,const char* Text); extern void DESTROY_MUTEX(yacco2::MUTEX& Mu); extern void CREATE_COND_VAR(yacco2::COND_VAR& Cv); extern void COND_WAIT(yacco2::COND_VAR& Cv,yacco2::MUTEX& Mu,yacco2::Parser& parser); extern void SIGNAL_COND_VAR(yacco2::Parser& To_thread,yacco2::Parser& parser); extern void DESTROY_COND_VAR(yacco2::COND_VAR& Cv); extern yacco2::THR_result@/ CREATE_THREAD(yacco2::Type_pp_fnct_ptr Thread,yacco2::Parser& Parser_requesting_parallelism); extern THREAD_NO THREAD_SELF(); @*2 Thread library implementation.\fbreak The wrapper functions shields the native library routines from Yacco2's callings. I call this a little middling sir... Please note, there is no exit or destroy thread wrapper routines. This is done automaticly when the thread returns to the operating system. For the duration of the parse, the thread stays within a work loop until it receives an ``exit'' message and its work status has been changed to |THREAD_TO_EXIT| by the requesting shutdown process. See |Parallel_threads_shutdown| routine. The exit message just interrupts the thread to start executing whose work loop condition has been broken. Basic hygiene takes place by the exiting thread and then it exits to the operating system with an appropriate return code. @*3 Microsoft's NT thread implementation. @= #if THREAD_LIBRARY_TO_USE__ == 1 @*4 Create mutex --- |CREATE_MUTEX|.\fbreak Appropriate defaults:\fbreak \ptindent{1) security: default} \ptindent{2) initial owner: OFF = no, ON = yes} \ptindent{3) named mutex: default 0 is no} @+= extern void yacco2::CREATE_MUTEX(yacco2::MUTEX& Mu){ Mu = CreateMutex(0,OFF,0); } @*4 Lock mutex --- |LOCK_MUTEX|. @+= extern void yacco2::LOCK_MUTEX(yacco2::MUTEX& Mu){ WaitForSingleObject(Mu,INFINITE); } @*4 Lock mutex --- |LOCK_MUTEX_OF_CALLED_PARSER|. @+= extern void yacco2::LOCK_MUTEX_OF_CALLED_PARSER (yacco2::MUTEX& Mu,yacco2::Parser&parser,const char* Text){ @; WaitForSingleObject(Mu,INFINITE); @; } @*4 Unlock mutex --- |UNLOCK_MUTEX|. @+= extern void yacco2::UNLOCK_MUTEX(yacco2::MUTEX& Mu){ ReleaseMutex(Mu); } @*4 Unlock mutex --- |UNLOCK_MUTEX_OF_CALLED_PARSER|. @+= extern void yacco2::UNLOCK_MUTEX_OF_CALLED_PARSER (yacco2::MUTEX& Mu,yacco2::Parser& parser,const char* Text){ @; ReleaseMutex(Mu); @; } @*4 Destroy mutex --- |DESTROY_MUTEX|. @+= extern void yacco2::DESTROY_MUTEX(yacco2::MUTEX& Mu){ CloseHandle(Mu); } @*4 Create conditional variable --- |CREATE_COND_VAR|.\fbreak Default settings:\fbreak \ptindent{1) security: default 0} \ptindent{2) initial cnt: 0 so that it can wait for a signal} \ptindent{3) max cnt: 1 so that it's 1:1} \ptindent{4) make unnamed variable: 0} @+= extern void yacco2::CREATE_COND_VAR(yacco2::COND_VAR& Cv){ COND_VAR xx = CreateSemaphore(0,0,1,0);// 0: wait state Cv = xx; } @*4 Conditional wait --- |COND_WAIT|.\fbreak Default settings:\fbreak \ptindent{unlock mutex} \ptindent{wait on cv} \ptindent{lock mu} @+= extern void yacco2::COND_WAIT(yacco2::COND_VAR& Cv,yacco2::MUTEX& Mu ,yacco2::Parser& parser){ @; UNLOCK_MUTEX_OF_CALLED_PARSER(Mu,parser," of self by COND_WAIT()"); WaitForSingleObject(Cv,INFINITE); LOCK_MUTEX_OF_CALLED_PARSER(Mu,parser," of self from wakened COND_WAIT()"); @; } @*4 Signal conditional variable --- |SIGNAL_COND_VAR|.\fbreak Default settings:\fbreak \ptindent{1) cond. var ptr} \ptindent{2) release count: make 1} \ptindent{3) previous cnt: 0 means don't use previous cnt: so make 1:1} @+= extern void yacco2::SIGNAL_COND_VAR(yacco2::Parser& To_thread,yacco2::Parser& parser){ @; ReleaseSemaphore(To_thread.cv__,1,0); @; } @*4 Destroy conditional variable --- |DESTROY_COND_VAR|. @+= extern void yacco2::DESTROY_COND_VAR(yacco2::COND_VAR& Cv){ CloseHandle(Cv); } @*4 Create thread --- |CREATE_THREAD|.\fbreak Default settings:\fbreak \ptindent{1) security: default 0} \ptindent{2) stack size: default 0} \ptindent{3) function addr} \ptindent{4) Parm list addr} \ptindent{5) initflag default 0: start executing right away} \ptindent{6) thread id addr} \fbreak When the thread is created, within the defining code body of the thread is a canned include file|wpp_core.h|. Its code sets all the variables related to thread activation: caller's parse context and launched number of threads. |pp_requesting_parallelism__| is the calling parser and so is |from_thread__|. The |no_competing_pp_ths__| is set from the calling parser's |no_requested_ths_to_run__|. |no_requested_ths_to_run__| is a readonly variable used to optimize mutex access / release of the calling parser's critical region. If the value is 1, there is no need to use the mutex. @+= extern yacco2::THR_result yacco2::CREATE_THREAD(yacco2::Type_pp_fnct_ptr Thread ,yacco2::Parser& Parser_requesting_parallelism){@/ yacco2::THREAD_NO thread_no; @; THR result = _beginthreadex(0,0,(Type_pp_fnct_ptr_voidp)Thread ,&Parser_requesting_parallelism,0,&thread_no); @; return result; } @*4 Thread id --- |THREAD_SELF|. @+= extern yacco2::THREAD_NO yacco2::THREAD_SELF(){ return GetCurrentThreadId(); } @*3 Pthreads implementation. @+= #elif THREAD_LIBRARY_TO_USE__ == 0 @*4 Create Mutex --- |CREATE_MUTEX|.\fbreak \fbreak When the thread is created, within the defining code body of the thread is a canned include file |wpp_core.h|. Its code sets all the variables related to thread activation: caller's parse context and launched number of threads. |pp_requesting_parallelism__| is the calling parser and so is |from_thread__|. The |no_competing_pp_ths__| is set from the calling parser's |no_requested_ths_to_run__|. |no_requested_ths_to_run__| is a readonly variable used to optimize mutex access / release of the calling parser's critical region. If the value is 1, there is no need to use the mutex. @+= extern void yacco2::CREATE_MUTEX(yacco2::MUTEX& Mu){ int result = pthread_mutex_init(&Mu,0); } @*4 Lock mutex --- |LOCK_MUTEX|. @+= extern void yacco2::LOCK_MUTEX(yacco2::MUTEX& Mu){ int result = pthread_mutex_lock(&Mu); } @*4 Lock mutex --- |LOCK_MUTEX_OF_CALLED_PARSER|. @+= extern void yacco2::LOCK_MUTEX_OF_CALLED_PARSER (yacco2::MUTEX& Mu,yacco2::Parser&parser,const char* Text){ @; int result = pthread_mutex_lock(&Mu); @; } @*4 Unlock mutex --- |UNLOCK_MUTEX|. @+= extern void yacco2::UNLOCK_MUTEX(yacco2::MUTEX& Mu){ int result = pthread_mutex_unlock(&Mu); } @*4 Unlock mutex --- |UNLOCK_MUTEX_OF_CALLED_PARSER|. @+= extern void yacco2::UNLOCK_MUTEX_OF_CALLED_PARSER(yacco2::MUTEX& Mu ,yacco2::Parser& parser,const char* Text){ @; int result = pthread_mutex_unlock(&Mu); @; } @*4 Destroy mutex --- |DESTROY_MUTEX|. @+= extern void yacco2::DESTROY_MUTEX(yacco2::MUTEX& Mu){ int result = pthread_mutex_destroy(&Mu); } @*4 Create conditional variable --- |CREATE_COND_VAR|. @+= extern void yacco2::CREATE_COND_VAR(yacco2::COND_VAR& Cv){ pthread_cond_init(&Cv,0); } @*4 Conditional wait --- |COND_WAIT|. @+= extern void yacco2::COND_WAIT(yacco2::COND_VAR& Cv,yacco2::MUTEX& Mu ,yacco2::Parser& parser){ @; if(yacco2::YACCO2_MU_GRAMMAR__){ @; yacco2::lrclog << parser.thread_no__ << "::" << parser.fsm_tbl__->id__ << "::" << " before release mutex by pthread_cond_wait()" << __FILE__ << __LINE__<< std::endl; @; } pthread_cond_wait(&Cv,&Mu); @; } @*4 Signal conditional variable --- |SIGNAL_COND_VAR|. @+= extern void yacco2::SIGNAL_COND_VAR(yacco2::Parser& To_thread,yacco2::Parser& parser){ @; pthread_cond_signal(&To_thread.cv__); @; } @*4 Destroy conditional variable --- |DESTROY_COND_VAR|. @+= extern void yacco2::DESTROY_COND_VAR(yacco2::COND_VAR& Cv){ pthread_cond_destroy(&Cv); } @*4 Create thread --- |CREATE_THREAD|. Experimenting with thread attributes by use of |pthread_attr_t| object and its methods: |pthread_attr_setstacksize|. If u want the default, pass |null| in the 2nd argument in |pthread_create|. This experiment is caused by VMS's tantrums when porting |pasxlator| translator to the Alpha platform. Circa 2002 -- 2003, this worked under VMS 7.2 and their older \CPLUSPLUS/ compiler 6.5. @+= extern yacco2::THR_result@/ yacco2::CREATE_THREAD@/ (yacco2::Type_pp_fnct_ptr Thread,yacco2::Parser& Parser_requesting_parallelism){@/ @; yacco2::THREAD_NO thread_no; pthread_attr_t alpha_attr; pthread_attr_init(&alpha_attr); #ifdef VMS__ pthread_attr_setstacksize(&alpha_attr,VMS_PTHREAD_STACK_SIZE__); #endif THR_result result = pthread_create(&thread_no,&alpha_attr ,(Type_pp_fnct_ptr_voidp)Thread,&Parser_requesting_parallelism); pthread_detach(thread_no); @; return result; } @*4 Thread id --- |THREAD_SELF|. @+= extern yacco2::THREAD_NO yacco2::THREAD_SELF(){ return pthread_self(); } @*2 Close off the wrapper conditional code. @+= #endif