Original version: Thu Aug 19 09:41:49 2010.
Last update: Mon Dec 11 12:30:37 2017.
This Web page contains two test programs, and their output, to give a demonstration of threaded programming with math library code.
Native vendor-provided thread support on Unix systems is highly platform dependent. Fortunately, all modern systems also provide the IEEE POSIX 1003.1-2004 Pthreads library, which allows multithreaded programming to work everywhere without code changes, except possibly for the issue of locks around selected code sections, as illustrated by the examples below.
You can download a file with this test program here.
/*********************************************************************** Demonstrate multithreading using the Pthreads library, and show that errno is handled correctly between threads. This version does NOT use locks around output statements. Tests have been run on FreeBSD (IA-32) GNU/Linux (Alpha, AMD64, IA-32, IA-64, PowerPC-32, PowerPC-64, SPARC) IRIX (MIPS) Mac OS X (IA-32 and PowerPC-32) MirBSD (IA-32) NetBSD (IA-32) OpenBSD (IA-32) Solaris (AMD64, IA-32, SPARC) with both -lm and -lmcw. With -lm, output is always correct. With -lmcw, serious output mixing occurs on IRIX, and minor mixing on FreeBSD. For a tutorial on Pthreads, see POSIX Threads Programming Blaise Barney, Lawrence Livermore National Laboratory https://computing.llnl.gov/tutorials/pthreads/ The standard is available here: IEEE Std 1003.1, 2004 Edition http://www.unix.org/version3/ieee_std.html Usage: c99 [-DMAXTEST=nnnnnn] [-DMAXTHREADS=nn] thread1.c -lpthread -lm && ./a.out cc [-DMAXTEST=nnnnnn] [-DMAXTHREADS=nn] thread1.c -lpthread -lm && ./a.out gcc [-DMAXTEST=nnnnnn] [-DMAXTHREADS=nn] thread1.c -lpthread -lm && ./a.out dgcc [-DMAXTEST=nnnnnn] [-DMAXTHREADS=nn] thread1.c -lpthread ../libmcw.a && ./a.out gcc [-DMAXTEST=nnnnnn] [-DMAXTHREADS=nn] thread1.c -lpthread ../libmcw.a && ./a.out Typical default output: ------------------------------------------------------------------------ The only output should be two begin/end lines from each thread. Any assertion failure means that errno has received an unexpected value. ------------------------------------------------------------------------ Begin normal thread 0 Begin underflow thread 2 Begin overflow thread 1 End normal thread 0 Begin normal thread 3 End normal thread 3 Begin overflow thread 4 Begin underflow thread 5 Begin normal thread 6 Begin overflow thread 7 End normal thread 6 End underflow thread 5 End underflow thread 2 End overflow thread 1 End overflow thread 4 End overflow thread 7 [19-Aug-2010] ***********************************************************************/ #include <assert.h> #include <errno.h> #include <float.h> #include <math.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #if !defined(MAXTEST) #define MAXTEST 1000000 #endif #if !defined(MAXTHREAD) #define MAXTHREAD 8 #endif void work_normal(long int id) { int k; (void)printf("Begin normal thread %ld\n", id); (void)fflush(stdout); for (k = 0; k < MAXTEST; ++k) { volatile double x, y; x = (double)k; errno = 0; y = sqrt(x); assert(errno == 0); } (void)printf("End normal thread %ld\n", id); (void)fflush(stdout); } void work_overflow(long int id) { int k; (void)printf("\tBegin overflow thread %ld\n", id); (void)fflush(stdout); for (k = 0; k < MAXTEST; ++k) { volatile double x, y; x = DBL_MAX; errno = 0; y = exp(x); if ( (errno != ERANGE) && (errno != 0) ) { (void)fprintf(stderr, "overflow thread sets errno = %d\t", errno); perror("perror() says errno means"); (void)fflush(stderr); assert((errno == ERANGE) || (errno == 0)); } } (void)printf("\tEnd overflow thread %ld\n", id); (void)fflush(stdout); } void work_underflow(long int id) { int k; (void)printf("\t\tBegin underflow thread %ld\n", id); (void)fflush(stdout); for (k = 0; k < MAXTEST; ++k) { volatile double x, y; x = DBL_MIN; errno = 0; y = exp(x); if ( (errno != ERANGE) && (errno != 0) ) { (void)fprintf(stderr, "underflow thread sets errno = %d\t", errno); perror("perror() says errno means"); (void)fflush(stderr); assert((errno == ERANGE) || (errno == 0)); } } (void)printf("\t\tEnd underflow thread %ld\n", id); (void)fflush(stdout); } void * thread_main(void *thread_id) { long int id; id = (long int)thread_id; switch (id % 3) { default: /* FALL THROUGH */ case 0: work_normal(id); break; case 1: work_overflow(id); break; case 2: work_underflow(id); break; } pthread_exit(NULL); return ((void *)NULL); /* NOT REACHED */ } int main(void) { pthread_t threads[MAXTHREAD]; int k, rc; (void)printf("------------------------------------------------------------------------\n"); (void)printf("The only output should be two begin/end lines from each thread.\n"); (void)printf("Any assertion failure means that errno has received an unexpected value.\n"); (void)printf("------------------------------------------------------------------------\n\n"); for (k = 0; k < MAXTHREAD; ++k) { /* ** Ignore harmless compiler warnings ** "cast to pointer from integer of different size" ** for last argument to pthread_create(). */ rc = pthread_create(&threads[k], NULL, thread_main, (void *)k); if (rc != 0) { (void)printf("ERROR: return code for thread %d [%ld] from pthread_create() = %d\n", k, (long int)threads[k], rc); return (EXIT_FAILURE); } } pthread_exit(NULL); (void)printf("Unexpected return from pthread_exit()\n"); /* NOT REACHED */ return (EXIT_SUCCESS); /* NOT REACHED */ }
On many modern systems, a compilation and run the test program looks something like this, although the interleaving of output output lines is essentially unpredictable because it depends on the order in which threads are scheduled.
On Oracle/Sun Solaris systems, compilation requires the -mt (multithreading) option to force errno to expand to a call to a thread-local function, instead of to the default single global integer variable.
% dgcc thread1.c -lpthread /usr/local/lib64/libmcw.a && ./a.out ------------------------------------------------------------------------ The only output should be two begin/end lines from each thread. Any assertion failure means that errno has received an unexpected value. ------------------------------------------------------------------------ Begin normal thread 0 Begin overflow thread 1 Begin underflow thread 2 Begin overflow thread 7 Begin normal thread 6 Begin normal thread 3 End overflow thread 1 End normal thread 3 Begin overflow thread 4 End normal thread 0 Begin underflow thread 5 End overflow thread 7 End normal thread 6 End underflow thread 2 End overflow thread 4 End underflow thread 5
However, on an old SGI IRIX MIPS system, output is seriously intermixed:
% cc thread1.c -lpthread /usr/local/lib/libmcw.a && ./a.out ------------------------------------------------------------------------ The only output should be two begin/end lines from each thread. Any assertion failure means that errno has received an unexpected value. ------------------------------------------------------------------------ Begin normal thread 0 B B B eB eBeBgeEBgegeigneigignidgnini n i n nn nnn o ooo oovurvrurvenmemnmerdaradarfelflelflr l r loftotftowlhwhlhw or ror tweteweth aha ahrtdrdtdreh e h ear3a0r6ade d e d a a 1d4dE7 E n 2n5d d n Eno EnorEndr mnd m ad oaEl ovln ove dtvert herfhurrflrnefloedalowaedow dr w t f3 th6l thr ohrewrea eadtad hd 1r 4 e7 a d 2 End underflow thread 5
That problem is easily solved by locking output operations, as demonstrated in the next section:
You can download a file with this test program here.
/*********************************************************************** Demonstrate multithreading using the Pthreads library, and show that errno is handled correctly between threads. This version uses LOCKS around output statements. Tests have been run on FreeBSD (IA-32) GNU/Linux (Alpha, AMD64, IA-32, IA-64, PowerPC-32, PowerPC-64, SPARC) IRIX (MIPS) Mac OS X (IA-32 and PowerPC-32) MirBSD (IA-32) NetBSD (IA-32) OpenBSD (IA-32) Solaris (AMD64, IA-32, SPARC) with both -lm and -lmcw. Locking solves the problems that occurs without lock: serious output mixing occurs on IRIX, and minor mixing on FreeBSD. For a tutorial on Pthreads, see POSIX Threads Programming Blaise Barney, Lawrence Livermore National Laboratory https://computing.llnl.gov/tutorials/pthreads/ The standard is available here: IEEE Std 1003.1, 2004 Edition http://www.unix.org/version3/ieee_std.html Usage: c99 [-DMAXTEST=nnnnnn] [-DMAXTHREADS=nn] thread2.c -lpthread -lm && ./a.out cc [-DMAXTEST=nnnnnn] [-DMAXTHREADS=nn] thread2.c -lpthread -lm && ./a.out gcc [-DMAXTEST=nnnnnn] [-DMAXTHREADS=nn] thread2.c -lpthread -lm && ./a.out dgcc [-DMAXTEST=nnnnnn] [-DMAXTHREADS=nn] thread2.c -lpthread ../libmcw.a && ./a.out gcc [-DMAXTEST=nnnnnn] [-DMAXTHREADS=nn] thread2.c -lpthread ../libmcw.a && ./a.out Typical default output: ------------------------------------------------------------------------ The only output should be two begin/end lines from each thread. Any assertion failure means that errno has received an unexpected value. ------------------------------------------------------------------------ Begin normal thread 0 Begin underflow thread 2 Begin overflow thread 1 End normal thread 0 Begin normal thread 3 End normal thread 3 Begin overflow thread 4 Begin underflow thread 5 Begin normal thread 6 Begin overflow thread 7 End normal thread 6 End underflow thread 5 End underflow thread 2 End overflow thread 1 End overflow thread 4 End overflow thread 7 [19-Aug-2010] ***********************************************************************/ #include <assert.h> #include <errno.h> #include <float.h> #include <math.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #if !defined(MAXTEST) #define MAXTEST 1000000 #endif #if !defined(MAXTHREAD) #define MAXTHREAD 8 #endif pthread_mutex_t the_lock; #define LOCK() pthread_mutex_lock(&the_lock) #define UNLOCK() pthread_mutex_unlock(&the_lock) void work_normal(long int id) { int k; LOCK(); (void)printf("Begin normal thread %ld\n", id); (void)fflush(stdout); UNLOCK(); for (k = 0; k < MAXTEST; ++k) { volatile double x, y; x = (double)k; errno = 0; y = sqrt(x); assert(errno == 0); } LOCK(); (void)printf("End normal thread %ld\n", id); (void)fflush(stdout); UNLOCK(); } void work_overflow(long int id) { int k; LOCK(); (void)printf("\tBegin overflow thread %ld\n", id); (void)fflush(stdout); UNLOCK(); for (k = 0; k < MAXTEST; ++k) { volatile double x, y; x = DBL_MAX; errno = 0; y = exp(x); if ( (errno != ERANGE) && (errno != 0) ) { LOCK(); (void)fprintf(stderr, "overflow thread sets errno = %d\t", errno); perror("perror() says errno means"); (void)fflush(stderr); UNLOCK(); assert((errno == ERANGE) || (errno == 0)); } } LOCK(); (void)printf("\tEnd overflow thread %ld\n", id); (void)fflush(stdout); UNLOCK(); } void work_underflow(long int id) { int k; LOCK(); (void)printf("\t\tBegin underflow thread %ld\n", id); (void)fflush(stdout); UNLOCK(); for (k = 0; k < MAXTEST; ++k) { volatile double x, y; x = DBL_MIN; errno = 0; y = exp(x); if ( (errno != ERANGE) && (errno != 0) ) { LOCK(); (void)fprintf(stderr, "underflow thread sets errno = %d\t", errno); perror("perror() says errno means"); (void)fflush(stderr); UNLOCK(); assert((errno == ERANGE) || (errno == 0)); } } LOCK(); (void)printf("\t\tEnd underflow thread %ld\n", id); (void)fflush(stdout); UNLOCK(); } void * thread_main(void *thread_id) { long int id; id = (long int)thread_id; switch (id % 3) { default: /* FALL THROUGH */ case 0: work_normal(id); break; case 1: work_overflow(id); break; case 2: work_underflow(id); break; } pthread_exit(NULL); return ((void *)NULL); /* NOT REACHED */ } int main(void) { pthread_t threads[MAXTHREAD]; int k, rc; (void)printf("------------------------------------------------------------------------\n"); (void)printf("The only output should be two begin/end lines from each thread.\n"); (void)printf("Any assertion failure means that errno has received an unexpected value.\n"); (void)printf("------------------------------------------------------------------------\n\n"); for (k = 0; k < MAXTHREAD; ++k) { /* ** Ignore harmless compiler warnings ** "cast to pointer from integer of different size" ** for last argument to pthread_create(). */ rc = pthread_create(&threads[k], NULL, thread_main, (void *)k); if (rc != 0) { LOCK(); (void)printf("ERROR: return code for thread %d [%ld] from pthread_create() = %d\n", k, (long int)threads[k], rc); UNLOCK(); return (EXIT_FAILURE); } } pthread_exit(NULL); (void)printf("Unexpected return from pthread_exit()\n"); /* NOT REACHED */ return (EXIT_SUCCESS); /* NOT REACHED */ }
Locking of output code sections solves the output mixing problem in this run on an old SGI IRIX MIPS system:
% cc thread2.c -lpthread /usr/local/lib/libmcw.a && ./a.out ------------------------------------------------------------------------ The only output should be two begin/end lines from each thread. Any assertion failure means that errno has received an unexpected value. ------------------------------------------------------------------------ Begin normal thread 0 Begin overflow thread 1 Begin underflow thread 2 Begin normal thread 3 Begin overflow thread 4 Begin underflow thread 5 Begin normal thread 6 Begin overflow thread 7 End normal thread 0 End normal thread 3 End overflow thread 1 End normal thread 6 End underflow thread 2 End overflow thread 4 End underflow thread 5 End overflow thread 7