Demonstration of threaded programming with math libraries

Valid HTML 4.0!

Original version: Thu Aug 19 09:41:49 2010.
Last update: Thu Aug 19 10:07:23 2010.

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.

Test program without locks

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 */
}

Output of test program without locks

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:

Test program without locks

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 */
}

Output of test program with locks

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