[sword-cvs] icu-sword/source/test/threadtest .cvsignore,NONE,1.1 Makefile.in,NONE,1.1 converttest.cpp,NONE,1.1 stringtest.cpp,NONE,1.1 threadtest.cpp,NONE,1.1 threadtest.dsp,NONE,1.1 threadtest.dsw,NONE,1.1 threadtest.h,NONE,1.1

sword@www.crosswire.org sword@www.crosswire.org
Tue, 9 Sep 2003 19:42:54 -0700


Update of /usr/local/cvsroot/icu-sword/source/test/threadtest
In directory www:/tmp/cvs-serv19862/source/test/threadtest

Added Files:
	.cvsignore Makefile.in converttest.cpp stringtest.cpp 
	threadtest.cpp threadtest.dsp threadtest.dsw threadtest.h 
Log Message:
ICU 2.6 commit

--- NEW FILE: .cvsignore ---
Makefile

--- NEW FILE: Makefile.in ---
## Makefile.in for ICU - test/threadtest
## Copyright (c) 2001, International Business Machines Corporation and
## others. All Rights Reserved.

## Source directory information
srcdir = @srcdir@
top_srcdir = @top_srcdir@

top_builddir = ../..

include $(top_builddir)/icudefs.mk

## Platform-specific setup
include @platform_make_fragment@

## Build directory information
subdir = test/threadtest

## Extra files to remove for 'make clean'
CLEANFILES = *~ $(DEPS)

## Target information
TARGET = threadtest

DEFS = @DEFS@
CPPFLAGS = @CPPFLAGS@ -I$(top_builddir)/common -I$(top_srcdir)/common -I$(top_srcdir)/i18n 
CFLAGS = @CFLAGS@
CXXFLAGS = @CXXFLAGS@
ENABLE_RPATH = @ENABLE_RPATH@
ifeq ($(ENABLE_RPATH),YES)
RPATHLDFLAGS = $(LD_RPATH)$(LD_RPATH_PRE)$(libdir)
endif
LDFLAGS = @LDFLAGS@ $(RPATHLDFLAGS)
LIBS = $(LIBICUI18N) $(LIBICUUC) @LIBS@ @LIB_M@

OBJECTS = threadtest.o stringtest.o converttest.o

DEPS = $(OBJECTS:.o=.d)

## List of phony targets
.PHONY : all all-local install install-local clean clean-local	\
distclean distclean-local dist dist-local check check-local

## Clear suffix list
.SUFFIXES :

## List of standard targets
all: all-local
install: install-local
clean: clean-local
distclean : distclean-local
dist: dist-local
check: all check-local

all-local: $(TARGET)

install-local:

dist-local:

clean-local:
	test -z "$(CLEANFILES)" || $(RMV) $(CLEANFILES)
	$(RMV) $(OBJECTS) $(TARGET)

distclean-local: clean-local
	$(RMV) Makefile

check-local: all-local

Makefile: $(srcdir)/Makefile.in  $(top_builddir)/config.status
	cd $(top_builddir) \
	 && CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status

$(TARGET) : $(OBJECTS)
	$(LINK.cc) -o $@ $^ $(LIBS)

invoke:
	ICU_DATA=$${ICU_DATA:-$(top_builddir)/data/} TZ=PST8PDT $(INVOKE) $(INVOCATION)

ifeq (,$(MAKECMDGOALS))
-include $(DEPS)
else
ifneq ($(patsubst %clean,,$(MAKECMDGOALS)),)
ifneq ($(patsubst %install,,$(MAKECMDGOALS)),)
-include $(DEPS)
endif
endif
endif


--- NEW FILE: converttest.cpp ---
//
//********************************************************************
//   Copyright (C) 2002, International Business Machines
//   Corporation and others.  All Rights Reserved.
//********************************************************************
//
// File converttest.cpp
//

#include "threadtest.h"
#include "unicode/utypes.h"
#include "unicode/ucnv.h"
#include "unicode/uclean.h"
#include "stdio.h"

U_CAPI UBool U_EXPORT2 ucnv_cleanup();

class ConvertThreadTest: public AbstractThreadTest {
public:
                    ConvertThreadTest();
    virtual        ~ConvertThreadTest();
    virtual void    check();
    virtual void    runOnce();

private:
    UConverter      *fCnv;
};


ConvertThreadTest::ConvertThreadTest() {
    UErrorCode    err = U_ZERO_ERROR;

    fCnv = ucnv_open("gb18030", &err);
    if (U_FAILURE(err)) {
        fprintf(stderr, "ConvertTest - could not ucnv_open(\"gb18030\")\n");
        fCnv = NULL;
    }
};


ConvertThreadTest::~ConvertThreadTest() {
    ucnv_close(fCnv);
    fCnv = 0;
}

void ConvertThreadTest::runOnce() {
    UErrorCode     err = U_ZERO_ERROR;
    UConverter     *cnv1;
    UConverter     *cnv2;
    char           buf[U_CNV_SAFECLONE_BUFFERSIZE];
    int32_t        bufSize = U_CNV_SAFECLONE_BUFFERSIZE;

    cnv1 = ucnv_open("shift_jis", &err);
    if (U_FAILURE(err)) {
        fprintf(stderr, "ucnv_open(\"shift_jis\") failed.\n");
    }

    cnv2 = ucnv_safeClone(fCnv,       // The source converter, common to all threads.
                          buf,  
                          &bufSize,  
                          &err);
    if (U_FAILURE(err)) {
        fprintf(stderr, "ucnv_safeClone() failed.\n");
    }
    ucnv_close(cnv1);
    ucnv_close(cnv2);
    ucnv_flushCache();
}

void ConvertThreadTest::check() {
    UErrorCode     err = U_ZERO_ERROR;

    if (fCnv) {ucnv_close(fCnv);}
    //if (ucnv_cleanup () == FALSE) {
    //    fprintf(stderr, "ucnv_cleanup() failed - cache was not empty.\n");
    //}
    fCnv = ucnv_open("gb18030", &err);
    if (U_FAILURE(err)) {
        fprintf(stderr, "ConvertTest::check() - could not redo ucnv_open(\"gb18030\")\n");
        fCnv = NULL;
    }
}


AbstractThreadTest *createConvertTest() {
    return new ConvertThreadTest();
}


--- NEW FILE: stringtest.cpp ---
//
//********************************************************************
//   Copyright (C) 2002, International Business Machines
//   Corporation and others.  All Rights Reserved.
//********************************************************************
//
// File stringtest.cpp
//

#include "threadtest.h"
#include "unicode/unistr.h"
#include "stdio.h"

class StringThreadTest: public AbstractThreadTest {
public:
                    StringThreadTest();
    virtual        ~StringThreadTest();
    virtual void    check();
    virtual void    runOnce();
            void    makeStringCopies(int recursionCount);

private:
    UnicodeString   *fCleanStrings;
    UnicodeString   *fSourceStrings;
};

StringThreadTest::StringThreadTest() {
    // cleanStrings and sourceStrings are separately initialized to the same values.
    // cleanStrings are never touched after in any remotely unsafe way.
    // sourceStrings are copied during the test, which will run their buffer's reference
    //    counts all over the place.
    fCleanStrings     = new UnicodeString[5];
    fSourceStrings    = new UnicodeString[5];

    fCleanStrings[0]  = "When sorrows come, they come not single spies, but in batallions.";
    fSourceStrings[0] = "When sorrows come, they come not single spies, but in batallions.";
    fCleanStrings[1]  = "Away, you scullion! You rampallion! You fustilarion! I'll tickle your catastrophe!";
    fSourceStrings[1] = "Away, you scullion! You rampallion! You fustilarion! I'll tickle your catastrophe!"; 
    fCleanStrings[2]  = "hot";
    fSourceStrings[2] = "hot"; 
    fCleanStrings[3]  = "";
    fSourceStrings[3] = ""; 
    fCleanStrings[4]  = "Tomorrow, and tomorrow, and tomorrow,\n"
                        "Creeps in this petty pace from day to day\n"
                        "To the last syllable of recorded time;\n"
                        "And all our yesterdays have lighted fools \n"
                        "The way to dusty death. Out, out brief candle!\n"
                        "Life's but a walking shadow, a poor player\n"
                        "That struts and frets his hour upon the stage\n"
                        "And then is heard no more. It is a tale\n"
                        "Told by and idiot, full of sound and fury,\n"
                        "Signifying nothing.\n";
    fSourceStrings[4] = "Tomorrow, and tomorrow, and tomorrow,\n"
                        "Creeps in this petty pace from day to day\n"
                        "To the last syllable of recorded time;\n"
                        "And all our yesterdays have lighted fools \n"
                        "The way to dusty death. Out, out brief candle!\n"
                        "Life's but a walking shadow, a poor player\n"
                        "That struts and frets his hour upon the stage\n"
                        "And then is heard no more. It is a tale\n"
                        "Told by and idiot, full of sound and fury,\n"
                        "Signifying nothing.\n";
};


StringThreadTest::~StringThreadTest() {
    delete [] fCleanStrings;
    delete [] fSourceStrings;
}


void   StringThreadTest::runOnce() {
    makeStringCopies(25);
}

void   StringThreadTest::makeStringCopies(int recursionCount) {
    UnicodeString firstGeneration[5];
    UnicodeString secondGeneration[5];
    UnicodeString thirdGeneration[5];
    UnicodeString fourthGeneration[5];

    // Make four generations of copies of the source strings, in slightly variant ways.
    //
    int i;
    for (i=0; i<5; i++) {
         firstGeneration[i]   = fSourceStrings[i];
         secondGeneration[i]  = firstGeneration[i];
         thirdGeneration[i]   = UnicodeString(secondGeneration[i]);
 //        fourthGeneration[i]  = UnicodeString("Lay on, MacDuff, And damn'd be him that first cries, \"Hold, enough!\"");
         fourthGeneration[i]  = UnicodeString();
         fourthGeneration[i]  = thirdGeneration[i];
    }


    // Recurse to make even more copies of the strings/
    //
    if (recursionCount > 0) {
        makeStringCopies(recursionCount-1);
    }


    // Verify that all four generations are equal.
    for (i=0; i<5; i++) {
        if (firstGeneration[i] !=  fSourceStrings[i]   ||
            firstGeneration[i] !=  secondGeneration[i] ||
            firstGeneration[i] !=  thirdGeneration[i]  ||
            firstGeneration[i] !=  fourthGeneration[i])
        {
            fprintf(stderr, "Error, strings don't compare equal.\n");
        }
    }

};
  

void   StringThreadTest::check() {
    //
    //  Check that the reference counts on the buffers for all of the source strings
    //     are one.   The ref counts will have run way up while the test threads
    //     make numerous copies of the strings, but at the top of the loop all
    //     of the copies should be gone.
    //
    int i;

    for (i=0; i<5; i++) {
        if (fSourceStrings[i].fFlags & UnicodeString::kRefCounted) {
            const UChar *buf = fSourceStrings[i].getBuffer();
            uint32_t refCount = fSourceStrings[i].refCount();
            if (refCount != 1) {
                fprintf(stderr, "\nFailure.  SourceString Ref Count was %d, should be 1.\n", refCount);
            }
        }
    }
};
  

//
//  Factory functoin for StringThreadTest.
//     C function lets ThreadTest create StringTests without needing StringThreadTest header.
// 
AbstractThreadTest  *createStringTest() {
    return new StringThreadTest();
};

--- NEW FILE: threadtest.cpp ---
//
//********************************************************************
//   Copyright (C) 2002, International Business Machines
//   Corporation and others.  All Rights Reserved.
//********************************************************************
//
// File threadtest.cpp
//

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#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 WIN32

#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 <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <sched.h>
#include <sys/timeb.h>


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;
}



--- NEW FILE: threadtest.dsp ---
# Microsoft Developer Studio Project File - Name="threadtest" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **

# TARGTYPE "Win32 (x86) Console Application" 0x0103

CFG=threadtest - Win32 Debug
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
!MESSAGE use the Export Makefile command and run
!MESSAGE 
!MESSAGE NMAKE /f "threadtest.mak".
!MESSAGE 
!MESSAGE You can specify a configuration when running NMAKE
!MESSAGE by defining the macro CFG on the command line. For example:
!MESSAGE 
!MESSAGE NMAKE /f "threadtest.mak" CFG="threadtest - Win32 Debug"
!MESSAGE 
!MESSAGE Possible choices for configuration are:
!MESSAGE 
!MESSAGE "threadtest - Win32 Release" (based on "Win32 (x86) Console Application")
!MESSAGE "threadtest - Win32 Debug" (based on "Win32 (x86) Console Application")
!MESSAGE "threadtest - Win64 Release" (based on "Win32 (x86) Console Application")
!MESSAGE "threadtest - Win64 Debug" (based on "Win32 (x86) Console Application")
!MESSAGE 

# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe

!IF  "$(CFG)" == "threadtest - Win32 Release"

# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release"
# PROP BASE Intermediate_Dir "Release"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Release"
# PROP Intermediate_Dir "Release"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS"  /FD /c
# ADD CPP /nologo /G6 /MD /W3 /GX /O2 /Ob2 /I "..\..\..\include" /I "..\..\..\source\common" /I "..\..\..\source\i18n" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS"  /FD /c
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
# ADD LINK32 kernel32.lib user32.lib shell32.lib icuuc.lib icuin.lib icutu.lib /nologo /subsystem:console /machine:I386 /libpath:"..\..\..\lib\\"

!ELSEIF  "$(CFG)" == "threadtest - Win32 Debug"

# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "Debug"
# PROP BASE Intermediate_Dir "Debug"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "Debug"
# PROP Intermediate_Dir "Debug"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS"  /FD /GZ /c
# ADD CPP /nologo /G6 /MDd /W3 /Gm /GX /ZI /Od /I "..\..\..\include" /I "..\..\..\source\common" /I "..\..\..\source\i18n" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR  /FD /GZ /c
# ADD BASE RSC /l 0x409 /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
# ADD LINK32 kernel32.lib user32.lib shell32.lib icuucd.lib icuind.lib icutud.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept /libpath:"..\..\..\lib\\"

!ELSEIF  "$(CFG)" == "threadtest - Win64 Release"

# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release"
# PROP BASE Intermediate_Dir "Release"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Release"
# PROP Intermediate_Dir "Release"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN64" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS"  /FD /c
# ADD CPP /nologo  /MD /W3    /I "..\..\..\include" /I "..\..\..\source\common" /I "..\..\..\source\i18n" /D"WIN64" /D"NDEBUG" /D"_CONSOLE" /D"_MBCS"  /FD /c /O2 /GX /Op /QIA64_fmaopt /D"_IA64_" /Zi /D"WIN64" /D"WIN32" /D"_AFX_NO_DAO_SUPPORT" /Zm600
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:IA64
# ADD LINK32 kernel32.lib user32.lib shell32.lib icuuc.lib icuin.lib icutu.lib /nologo /subsystem:console /machine:IA64 /libpath:"..\..\..\lib\\" /incremental:no

!ELSEIF  "$(CFG)" == "threadtest - Win64 Debug"

# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "Debug"
# PROP BASE Intermediate_Dir "Debug"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "Debug"
# PROP Intermediate_Dir "Debug"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN64" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS"  /FD /GZ /c
# ADD CPP /nologo  /MDd /W3 /Gm    /I "..\..\..\include" /I "..\..\..\source\common" /I "..\..\..\source\i18n" /D"WIN64" /D"_DEBUG" /D"_CONSOLE" /D"_MBCS" /FR  /FD /GZ /c /Od /GX /Op /QIA64_fmaopt /D"_IA64_" /Zi /D"WIN64" /D"WIN32" /D"_AFX_NO_DAO_SUPPORT" /Zm600
# ADD BASE RSC /l 0x409 /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:IA64 /pdbtype:sept
# ADD LINK32 kernel32.lib user32.lib shell32.lib icuucd.lib icuind.lib icutud.lib /nologo /subsystem:console /debug /machine:IA64 /pdbtype:sept /libpath:"..\..\..\lib\\" /incremental:no

!ENDIF 

# Begin Target

# Name "threadtest - Win32 Release"
# Name "threadtest - Win32 Debug"
# Name "threadtest - Win64 Release"
# Name "threadtest - Win64 Debug"
# Begin Group "Source Files"

# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File

SOURCE=.\converttest.cpp
# End Source File
# Begin Source File

SOURCE=.\stringtest.cpp
# End Source File
# Begin Source File

SOURCE=.\threadtest.cpp
# End Source File
# End Group
# Begin Group "Header Files"

# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File

SOURCE=.\threadtest.h
# End Source File
# End Group
# Begin Group "Resource Files"

# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
# End Group
# End Target
# End Project

--- NEW FILE: threadtest.dsw ---
Microsoft Developer Studio Workspace File, Format Version 6.00
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!

###############################################################################

Project: "threadtest"=.\threadtest.dsp - Package Owner=<4>

Package=<5>
{{{
}}}

Package=<4>
{{{
}}}

###############################################################################

Global:

Package=<5>
{{{
}}}

Package=<3>
{{{
}}}

###############################################################################


--- NEW FILE: threadtest.h ---
//
//********************************************************************
//   Copyright (C) 2002, International Business Machines
//   Corporation and others.  All Rights Reserved.
//********************************************************************
//
// File threadtest.h
//
#ifndef ABSTRACTTHREADTEST_H
#define ABSTRACTTHREADTEST_H
//------------------------------------------------------------------------------
//
//   class AbstractThreadTest    Base class for threading tests.
//                         Use of this abstract base isolates the part of the
//                         program that nows how to spin up and control threads
//                         from the specific stuff being tested, and (hopefully)
//                         simplifies adding new threading tests for different parts
//                         of ICU.
//
//     Derived classes:    A running test will have exactly one instance of a
//                         derived class, which will persist for the duration of the
//                         test and be shared among all of the threads involved in
//                         the test.
//
//                         The constructor will be called in a single-threaded environment,
//                         and should set up any data that will need to persist for the
//                         duration.
//
//                         runOnce() will be called repeatedly by the working threads of
//                         the test in the full multi-threaded environment.
//
//                         check() will be called periodically in a single threaded
//                         environment, with the worker threads temporarily suspended between
//                         between calls to runOnce().  Do consistency checks here.
//
//------------------------------------------------------------------------------
class AbstractThreadTest {
public:
                     AbstractThreadTest() {};
    virtual         ~AbstractThreadTest() {};
    virtual void     check()   = 0;
    virtual void     runOnce() = 0;
};

#endif // ABSTRACTTHREADTEST_H