/////////////////////////////////////////////////////////////// // queue.cpp -- test multi-threaded queuing // // ver 3.1 // // Language: Visual C++, ver 6.0 // // Platform: Micron Dual Pentium Pro 200, Win NT 4.0 // // Application: Demo for CSE691, Pr#3, Fall 2001 // // Author: Jim Fawcett, CST 2-187, Syracuse Univ. // // (315) 443-3948, jfawcett@twcny.rr.com // /////////////////////////////////////////////////////////////// // Operations: // /////////////////////////////////////////////////////////////// // This program demonstrates synchronizing access to a // // queue by an enQing thread and a deQing thread. It also // // demonstrates synchronizing access to an output stream // // being accessed by two child threads and a parent thread. // // // // - Note that the shared queue is not declared globally, // // It is declared in main and a pointer to it is passed // // to each thread function needing access. In this // // example, two queues are created. One to demonstrate // // passing integers between two threads, another to dem- // // onstrate passing strings between two threads, so you // // will see four thread processing functions here. // // - Thread th1 enqueues integers, announcing its actions // // as they occur. // // - Thread th2 dequeues the integer messages and announces // // the result. // // - Threads th3 and th4 enqueue and dequeue strings, in // // the same way. // // - All calls into each of the mtqueue objects are synch- // // ronized with an object of a lockingPtr class, from // // the module of that name [Alexandrescu, C/C++ User's // // Journal, February 2001]. // // > This has the advantage that, complex synchronizing // // code is no longer needed in mtqueue. // // > also, any object, say a container from the STL, can // // be synchronized in the same way. // // > All you do is to qualify the object declaration with // // the keyword volatile, declare an instance of // // lockingPtr and use it for all access to the object. // // // /////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////// // Build Process // /////////////////////////////////////////////////////////////// // Files Required: // // queue.cpp, mtqueue.h, nodePool.h // // // // Compiler Command: // // cl /GX /MT /D "_X86_" queue.cpp mtqueue.cpp // // // // Integrated Environment: // // Project\Settings\C++\CodeGeneration\DebugMultithreaded // // // /////////////////////////////////////////////////////////////// /* Maintenance History =================== ver 3.1 : 11 Oct 2001 - fixed bug due to use of locally defined Mutex objects which synchronize access at one code location, but not access of the same object in two different locations. That is easily fixed by making the mutex objects global - see code and comment below. - fixed a performance problem caused by larger than necessary protected code region, due to the fact that the Mutex does not unlock until it goes out of scope at the end of the function. This is easily fixed by using an anonymous (un-named) lockingPtr. This creates a temporary object which goes out of scope immediately following the invocation. ver 3.0 : 10 Oct 2001 - added two additional thread functions to demonstrate queuing with strings. - accomodated changes in mtqueue by using lockingPtrs in the thread processing functions. ver 2.0 : 10 Oct 2000 - added syncOut class to synchronize access to cout ver 1.0 : 14 Nov 1998 - first release */ #include // #include // _beginthread(...), CriticalSection func's #include // cout, << #include // setw() #include // ostringstream(...) #include "mtqueue.h" // mtqueue sq; #include "lockingPtr.h" // lockingPtr pQ(sq,mtx); using namespace std; ////////////////////////////////////////////////////// // global mutexs to protect global cout and both // queues, accessed from two different locations Mutex outMtx; Mutex iqMtx, sqMtx; // iqMtx and sqMtx need to be global since each queue is accessed from // two different code locations. Thus to prevent simultaneous access // from the reader in th2Proc and the writer in th1Proc they need to // share the same mutex, e.g., iqMtx. // const int MaxCount = 50; //----< enqueuing thread function >---------------------------- void __cdecl th1Proc(void* pQueue) { volatile mtqueue *pQ = (volatile mtqueue*)pQueue; int tid = GetCurrentThreadId(); int count = 0; do { int msg = count; outMtx.lock(); cout << "\n sending msg = " << setw(2) << msg << " with thread " << tid; cout.flush(); outMtx.unlock(); // note use of temporary lockingPtr. Since it is an anonymous // temporary object, it will be constructed just before use // and destroyed just after use, making the critical section // just those lines where used. lockingPtr< mtqueue >(*pQ,iqMtx)->enQ(msg); // You can change relative timing between // threads by using these sleep functions. Sleep(10); } while(++count < MaxCount); outMtx.lock(); cout << "\n\nfinished enQing on thread " << tid << endl; cout.flush(); outMtx.unlock(); } // //----< dequeuing thread function >---------------------------- void __cdecl th2Proc(void *pQueue) { mtqueue* pQ = (mtqueue*)pQueue; int tid = GetCurrentThreadId(); int count = 0; while(count++ < MaxCount) { // see note about temporary lockingPtr in th1Proc while(lockingPtr< mtqueue >(*pQ,iqMtx)->length() == 0) Sleep(20); int msg = lockingPtr< mtqueue >(*pQ,iqMtx)->deQ(); outMtx.lock(); cout << "\n recving msg = " << setw(2) << msg << " with thread " << tid; cout.flush(); outMtx.unlock(); // You can change relative timing between // threads by using these sleep functions. // Sleep(20); }; outMtx.lock(); cout << "\n\nfinished deQing on thread " << tid << endl; cout.flush(); outMtx.unlock(); } // //----< enqueuing thread function >---------------------------- void __cdecl th3Proc(void* pQueue) { mtqueue *pQ = (mtqueue*)pQueue; int tid = GetCurrentThreadId(); int count = 0; do { ostringstream temp; int msg = count; temp << "message#" << count << ends; outMtx.lock(); cout << "\n sending msg = " << setw(11) << temp.str().c_str() << " with thread " << tid; cout.flush(); outMtx.unlock(); // see note about temporary lockingPtr in th1Proc lockingPtr< mtqueue >(*pQ,sqMtx)->enQ(temp.str()); // You can change relative timing between // threads by using these sleep functions. Sleep(10); } while(++count < MaxCount); outMtx.lock(); cout << "\n\nfinished enQing on thread " << tid << endl; outMtx.unlock(); } // //----< dequeuing thread function >---------------------------- void __cdecl th4Proc(void *pQueue) { volatile mtqueue* pQ = (volatile mtqueue*)pQueue; int tid = GetCurrentThreadId(); int count = 0; while(count++ < MaxCount) { // see note about temporary lockingPtr in th1Proc while(lockingPtr< mtqueue >(*pQ,sqMtx)->length() == 0) Sleep(20); string msg = lockingPtr< mtqueue >(*pQ,sqMtx)->deQ(); outMtx.lock(); cout << "\n recving msg = " << setw(11) << msg << " with thread " << tid; cout.flush(); outMtx.unlock(); // You can change relative timing between // threads by using these sleep functions. // Sleep(20); }; outMtx.lock(); cout << "\n\nfinished deQing on thread " << tid << endl; cout.flush(); outMtx.unlock(); } // //----< create threads and wait for them to finish >----------- void main() { const char *msg1 = "\n Testing Concurrent Reads and Writes "; const char *msg2 = "\n=====================================\n"; outMtx.lock(); cout << msg1 << msg2; outMtx.unlock(); volatile mtqueue Q1; HANDLE thH[2]; thH[0] = (HANDLE)_beginthread(th1Proc,0,(void*)&Q1); // post int msgs thH[1] = (HANDLE)_beginthread(th2Proc,0,(void*)&Q1); // retrieve ints outMtx.lock(); cout << "\nmain thread waiting now" << endl; outMtx.unlock(); WaitForMultipleObjects(2,thH,TRUE,INFINITE); CloseHandle(thH[0]); CloseHandle(thH[1]); volatile mtqueue Q2; thH[0] = (HANDLE)_beginthread(th3Proc,0,(void*)&Q2); // post string msgs thH[1] = (HANDLE)_beginthread(th4Proc,0,(void*)&Q2); // retrieve strings outMtx.lock(); cout << "\nmain thread waiting again" << endl; outMtx.unlock(); WaitForMultipleObjects(2,thH,TRUE,INFINITE); outMtx.lock(); cout << "\nmain thread ending now" << endl << endl; outMtx.unlock(); CloseHandle(thH[0]); CloseHandle(thH[1]); }