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