rand() is not required to be multithread-safe. So, it's not a bug when rand()
is not MT-safe on some platforms; it's merely a portability problem worth
documenting.

The attached program tests the MT-safety. Results: OK on all platforms, except

FreeBSD 13.2  Expected value #7504 not found in multithreaded results. (4 
failures among 100 runs)
macOS 12.5    Expected value #31 not found in multithreaded results.
Solaris 10    Expected value #95981 not found in multithreaded results.
Solaris 11.4  Expected value #55372 not found in multithreaded results.
OpenBSD 7.4   rand() is non-deterministic.
Cygwin 3.4.6  rand() is non-deterministic.

The latter statement regarding Cygwin is not correct. Looking into the Cygwin
source code, it's clear that the problem is that srand() has no effect on
other thread - which is a bug since it violates ISO C 23.

On Haiku, rand() is MT-safe while random() is not.


2023-11-10  Bruno Haible  <br...@clisp.org>

        doc: Mention rand and srand limitations.
        * doc/posix-functions/rand.texi: Mention multithread-safety problem.
        * doc/posix-functions/srand.texi: Mention a Cygwin bug.

diff --git a/doc/posix-functions/rand.texi b/doc/posix-functions/rand.texi
index 2f36a548a7..48b0bc5758 100644
--- a/doc/posix-functions/rand.texi
+++ b/doc/posix-functions/rand.texi
@@ -18,4 +18,7 @@
 @item
 This function is only defined as an inline function on some platforms:
 Android 4.4.
+@item
+This function is not multithread-safe on some platforms:
+macOS 12.5, FreeBSD 13.2, Solaris 11.4.
 @end itemize
diff --git a/doc/posix-functions/srand.texi b/doc/posix-functions/srand.texi
index 3e8f4b429b..710a5a1270 100644
--- a/doc/posix-functions/srand.texi
+++ b/doc/posix-functions/srand.texi
@@ -15,4 +15,8 @@
 @item
 This function is only defined as an inline function on some platforms:
 Android 4.4.
+@item
+This function has no effect on @code{rand} invocations in other threads
+on some platforms:
+Cygwin 3.4.6.
 @end itemize
/* Multithread-safety test for rand().
   Copyright (C) 2023 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Written by Bruno Haible <br...@clisp.org>, 2023.  */

/* Whether to help the scheduler through explicit yield().
   Uncomment this to see if the operating system has a fair scheduler.  */
#define EXPLICIT_YIELD 1

/* Number of simultaneous threads.  */
#define THREAD_COUNT 4

/* Number of rand() invocations operations performed in each thread.
   This value is chosen so that the unit test terminates quickly.
   To reliably determine whether a rand() implementation is multithread-safe,
   set REPEAT_COUNT to 1000000 and run the test 100 times:
     $ for i in `seq 100`; do ./test-random-mt; done
 */
#define REPEAT_COUNT 1000000

/* Specification.  */
#include <stdlib.h>

#include <assert.h>
#include <pthread.h>
#include <stdio.h>

#if EXPLICIT_YIELD
# include <sched.h>
# define yield() sched_yield ()
#else
# define yield()
#endif

/* This test runs REPEAT_COUNT invocations of rand() in each thread and stores
   the result, then compares the first REPEAT_COUNT among these
     THREAD_COUNT * REPEAT_COUNT
   random numbers against a precomputed sequence with the same seed.  */

static void *
random_invocator_thread (void *arg)
{
  int *storage = (int *) arg;
  int repeat;

  for (repeat = 0; repeat < REPEAT_COUNT; repeat++)
    {
      storage[repeat] = rand ();
      yield ();
    }

  return NULL;
}

int
main ()
{
  unsigned int seed = 19891109;

  /* First, get the expected sequence of rand() results.  */
  srand (seed);
  int *expected = (int *) malloc (REPEAT_COUNT * sizeof (int));
  assert (expected != NULL);
  {
    int repeat;
    for (repeat = 0; repeat < REPEAT_COUNT; repeat++)
      expected[repeat] = rand ();
  }

  /* Then, run REPEAT_COUNT invocations of rand() each, in THREAD_COUNT
     separate threads.  */
  pthread_t threads[THREAD_COUNT];
  int *thread_results[THREAD_COUNT];
  srand (seed);
  {
    int i;
    for (i = 0; i < THREAD_COUNT; i++)
      {
        thread_results[i] = (int *) malloc (REPEAT_COUNT * sizeof (int));
        assert (thread_results[i] != NULL);
      }
    for (i = 0; i < THREAD_COUNT; i++)
      assert (pthread_create (&threads[i], NULL, random_invocator_thread, thread_results[i]) == 0);
  }

  /* Wait for the threads to terminate.  */
  {
    int i;
    for (i = 0; i < THREAD_COUNT; i++)
      assert (pthread_join (threads[i], NULL) == 0);
  }

  /* Finally, determine whether the threads produced the same sequence of
     rand() results.  */
  {
    int expected_index;
    int result_index[THREAD_COUNT];
    int i;

    for (i = 0; i < THREAD_COUNT; i++)
      result_index[i] = 0;

    for (expected_index = 0; expected_index < REPEAT_COUNT; expected_index++)
      {
        int expected_value = expected[expected_index];

        for (i = 0; i < THREAD_COUNT; i++)
          {
            if (thread_results[i][result_index[i]] == expected_value)
              {
                result_index[i]++;
                break;
              }
          }
        if (i == THREAD_COUNT)
          {
            if (expected_index == 0)
              {
                /* This occurs on platforms like OpenBSD, where srand() has no
                   effect and rand() always return non-deterministic values.
                   Mark the test as SKIP.  */
                fprintf (stderr, "Skipping test: rand() is non-deterministic.\n");
                return 77;
              }
            else
              {
                fprintf (stderr, "Expected value #%d not found in multithreaded results.\n",
                         expected_index);
                return 1;
              }
          }
      }
  }

  return 0;
}

Reply via email to