// //******************************************************************** // Copyright (C) 2002-2005, International Business Machines // Corporation and others. All Rights Reserved. //******************************************************************** // // File threadtest.cpp // #include #include #include #include "unicode/utypes.h" #include "unicode/uclean.h" #include "umutex.h" #include "threadtest.h" //------------------------------------------------------------------------------ // // Factory functions for creating different test types. // //------------------------------------------------------------------------------ extern AbstractThreadTest *createStringTest(); extern AbstractThreadTest *createConvertTest(); //------------------------------------------------------------------------------ // // Windows specific code for starting threads // //------------------------------------------------------------------------------ #ifdef U_WINDOWS #include "Windows.h" #include "process.h" typedef void (*ThreadFunc)(void *); class ThreadFuncs // This class isolates OS dependent threading { // functions from the rest of ThreadTest program. public: static void Sleep(int millis) {::Sleep(millis);}; static void startThread(ThreadFunc, void *param); static unsigned long getCurrentMillis(); static void yield() {::Sleep(0);}; }; void ThreadFuncs::startThread(ThreadFunc func, void *param) { unsigned long x; x = _beginthread(func, 0x10000, param); if (x == -1) { fprintf(stderr, "Error starting thread. Errno = %d\n", errno); exit(-1); } } unsigned long ThreadFuncs::getCurrentMillis() { return (unsigned long)::GetTickCount(); } // #elif defined (POSIX) #else //------------------------------------------------------------------------------ // // UNIX specific code for starting threads // //------------------------------------------------------------------------------ #include #include #include #include #include extern "C" { typedef void (*ThreadFunc)(void *); typedef void *(*pthreadfunc)(void *); class ThreadFuncs // This class isolates OS dependent threading { // functions from the rest of ThreadTest program. public: static void Sleep(int millis); static void startThread(ThreadFunc, void *param); static unsigned long getCurrentMillis(); static void yield() {sched_yield();}; }; void ThreadFuncs::Sleep(int millis) { int seconds = millis/1000; if (seconds <= 0) seconds = 1; ::sleep(seconds); } void ThreadFuncs::startThread(ThreadFunc func, void *param) { unsigned long x; pthread_t tId; //thread_t tId; #if defined(_HP_UX) && defined(XML_USE_DCE) x = pthread_create( &tId, pthread_attr_default, (pthreadfunc)func, param); #else pthread_attr_t attr; pthread_attr_init(&attr); x = pthread_create( &tId, &attr, (pthreadfunc)func, param); #endif if (x == -1) { fprintf(stderr, "Error starting thread. Errno = %d\n", errno); exit(-1); } } unsigned long ThreadFuncs::getCurrentMillis() { timeb aTime; ftime(&aTime); return (unsigned long)(aTime.time*1000 + aTime.millitm); } } // #else // #error This platform is not supported #endif //------------------------------------------------------------------------------ // // struct runInfo Holds the info extracted from the command line and data // that is shared by all threads. // There is only one of these, and it is static. // During the test, the threads will access this info without // any synchronization. // //------------------------------------------------------------------------------ const int MAXINFILES = 25; struct RunInfo { bool quiet; bool verbose; int numThreads; int totalTime; int checkTime; AbstractThreadTest *fTest; bool stopFlag; bool exitFlag; int32_t runningThreads; }; //------------------------------------------------------------------------------ // // struct threadInfo Holds information specific to an individual thread. // One of these is set up for each thread in the test. // The main program monitors the threads by looking // at the status stored in these structs. // //------------------------------------------------------------------------------ struct ThreadInfo { bool fHeartBeat; // Set true by the thread each time it finishes // a test. unsigned int fCycles; // Number of cycles completed. int fThreadNum; // Identifying number for this thread. ThreadInfo() { fHeartBeat = false; fCycles = 0; fThreadNum = -1; } }; // //------------------------------------------------------------------------------ // // Global Data // //------------------------------------------------------------------------------ RunInfo gRunInfo; ThreadInfo *gThreadInfo; UMTX gStopMutex; // Lets main thread suspend test threads. UMTX gInfoMutex; // Synchronize access to data passed between // worker threads and the main thread //---------------------------------------------------------------------- // // parseCommandLine Read through the command line, and save all // of the options in the gRunInfo struct. // // Display the usage message if the command line // is no good. // // Probably ought to be a member function of RunInfo. // //---------------------------------------------------------------------- void parseCommandLine(int argc, char **argv) { gRunInfo.quiet = false; // Set up defaults for run. gRunInfo.verbose = false; gRunInfo.numThreads = 2; gRunInfo.totalTime = 0; gRunInfo.checkTime = 10; try // Use exceptions for command line syntax errors. { int argnum = 1; while (argnum < argc) { if (strcmp(argv[argnum], "-quiet") == 0) gRunInfo.quiet = true; else if (strcmp(argv[argnum], "-verbose") == 0) gRunInfo.verbose = true; else if (strcmp(argv[argnum], "--help") == 0 || (strcmp(argv[argnum], "?") == 0)) {throw 1; } else if (strcmp(argv[argnum], "-threads") == 0) { ++argnum; if (argnum >= argc) throw 1; gRunInfo.numThreads = atoi(argv[argnum]); if (gRunInfo.numThreads < 0) throw 1; } else if (strcmp(argv[argnum], "-time") == 0) { ++argnum; if (argnum >= argc) throw 1; gRunInfo.totalTime = atoi(argv[argnum]); if (gRunInfo.totalTime < 1) throw 1; } else if (strcmp(argv[argnum], "-ctime") == 0) { ++argnum; if (argnum >= argc) throw 1; gRunInfo.checkTime = atoi(argv[argnum]); if (gRunInfo.checkTime < 1) throw 1; } else if (strcmp(argv[argnum], "string") == 0) { gRunInfo.fTest = createStringTest(); } else if (strcmp(argv[argnum], "convert") == 0) { gRunInfo.fTest = createConvertTest(); } else { fprintf(stderr, "Unrecognized command line option. Scanning \"%s\"\n", argv[argnum]); throw 1; } argnum++; } // We've reached the end of the command line parameters. // Fail if no test name was specified. if (gRunInfo.fTest == NULL) { fprintf(stderr, "No test specified.\n"); throw 1; } } catch (int) { fprintf(stderr, "usage: threadtest [-threads nnn] [-time nnn] [-quiet] [-verbose] test-name\n" " -quiet Suppress periodic status display. \n" " -verbose Display extra messages. \n" " -threads nnn Number of threads. Default is 2. \n" " -time nnn Total time to run, in seconds. Default is forever.\n" " -ctime nnn Time between extra consistency checks, in seconds. Default 10\n" " testname string | convert\n" ); exit(1); } } //---------------------------------------------------------------------- // // threadMain The main function for each of the swarm of test threads. // Run in a loop, executing the runOnce() test function each time. // // //---------------------------------------------------------------------- extern "C" { void threadMain (void *param) { ThreadInfo *thInfo = (ThreadInfo *)param; if (gRunInfo.verbose) printf("Thread #%d: starting\n", thInfo->fThreadNum); umtx_atomic_inc(&gRunInfo.runningThreads); // // while (true) { if (gRunInfo.verbose ) printf("Thread #%d: starting loop\n", thInfo->fThreadNum); // // If the main thread is asking us to wait, do so by locking gStopMutex // which will block us, since the main thread will be holding it already. // umtx_lock(&gInfoMutex); UBool stop = gRunInfo.stopFlag; // Need mutex for processors with flakey memory models. umtx_unlock(&gInfoMutex); if (stop) { if (gRunInfo.verbose) { fprintf(stderr, "Thread #%d: suspending\n", thInfo->fThreadNum); } umtx_atomic_dec(&gRunInfo.runningThreads); while (gRunInfo.stopFlag) { umtx_lock(&gStopMutex); umtx_unlock(&gStopMutex); } umtx_atomic_inc(&gRunInfo.runningThreads); if (gRunInfo.verbose) { fprintf(stderr, "Thread #%d: restarting\n", thInfo->fThreadNum); } } // // The real work of the test happens here. // gRunInfo.fTest->runOnce(); umtx_lock(&gInfoMutex); thInfo->fHeartBeat = true; thInfo->fCycles++; UBool exitNow = gRunInfo.exitFlag; umtx_unlock(&gInfoMutex); // // If main thread says it's time to exit, break out of the loop. // if (exitNow) { break; } } umtx_atomic_dec(&gRunInfo.runningThreads); // Returning will kill the thread. return; } } //---------------------------------------------------------------------- // // main // //---------------------------------------------------------------------- int main (int argc, char **argv) { // // Parse the command line options, and create the specified kind of test. // parseCommandLine(argc, argv); // // Fire off the requested number of parallel threads // if (gRunInfo.numThreads == 0) exit(0); gRunInfo.exitFlag = FALSE; gRunInfo.stopFlag = TRUE; // Will cause the new threads to block umtx_lock(&gStopMutex); gThreadInfo = new ThreadInfo[gRunInfo.numThreads]; int threadNum; for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { gThreadInfo[threadNum].fThreadNum = threadNum; ThreadFuncs::startThread(threadMain, &gThreadInfo[threadNum]); } unsigned long startTime = ThreadFuncs::getCurrentMillis(); int elapsedSeconds = 0; int timeSinceCheck = 0; // // Unblock the threads. // gRunInfo.stopFlag = FALSE; // Unblocks the worker threads. umtx_unlock(&gStopMutex); // // Loop, watching the heartbeat of the worker threads. // Each second, // display "+" if all threads have completed at least one loop // display "." if some thread hasn't since previous "+" // Each "ctime" seconds, // Stop all the worker threads at the top of their loop, then // call the test's check function. // while (gRunInfo.totalTime == 0 || gRunInfo.totalTime > elapsedSeconds) { ThreadFuncs::Sleep(1000); // We sleep while threads do their work ... if (gRunInfo.quiet == false && gRunInfo.verbose == false) { char c = '+'; int threadNum; umtx_lock(&gInfoMutex); for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { if (gThreadInfo[threadNum].fHeartBeat == false) { c = '.'; break; }; } umtx_unlock(&gInfoMutex); fputc(c, stdout); fflush(stdout); if (c == '+') for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) gThreadInfo[threadNum].fHeartBeat = false; } // // Update running times. // timeSinceCheck -= elapsedSeconds; elapsedSeconds = (ThreadFuncs::getCurrentMillis() - startTime) / 1000; timeSinceCheck += elapsedSeconds; // // Call back to the test to let it check its internal validity // if (timeSinceCheck >= gRunInfo.checkTime) { if (gRunInfo.verbose) { fprintf(stderr, "Main: suspending all threads\n"); } umtx_lock(&gStopMutex); // Block the worker threads at the top of their loop gRunInfo.stopFlag = TRUE; for (;;) { umtx_lock(&gInfoMutex); UBool done = gRunInfo.runningThreads == 0; umtx_unlock(&gInfoMutex); if (done) { break;} ThreadFuncs::yield(); } gRunInfo.fTest->check(); if (gRunInfo.quiet == false && gRunInfo.verbose == false) { fputc('C', stdout); } if (gRunInfo.verbose) { fprintf(stderr, "Main: starting all threads.\n"); } gRunInfo.stopFlag = FALSE; // Unblock the worker threads. umtx_unlock(&gStopMutex); timeSinceCheck = 0; } }; // // Time's up, we are done. (We only get here if this was a timed run) // Tell the threads to exit. // gRunInfo.exitFlag = true; for (;;) { umtx_lock(&gInfoMutex); UBool done = gRunInfo.runningThreads == 0; umtx_unlock(&gInfoMutex); if (done) { break;} ThreadFuncs::yield(); } // // Tally up the total number of cycles completed by each of the threads. // double totalCyclesCompleted = 0; for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { totalCyclesCompleted += gThreadInfo[threadNum].fCycles; } double cyclesPerMinute = totalCyclesCompleted / (double(gRunInfo.totalTime) / double(60)); printf("\n%8.1f cycles per minute.", cyclesPerMinute); // // Memory should be clean coming out // delete gRunInfo.fTest; delete [] gThreadInfo; umtx_destroy(&gInfoMutex); umtx_destroy(&gStopMutex); u_cleanup(); return 0; }