/* Test of simple atomic operations for multithreading.
Copyright (C) 2021-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 . */
/* Written by Bruno Haible , 2021. */
#include
#include "simple-atomic.h"
#if USE_ISOC_THREADS || USE_POSIX_THREADS || USE_ISOC_AND_POSIX_THREADS || USE_WINDOWS_THREADS
/* 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 operations performed in each thread.
With a smaller count, say 100, we often get an "OK" result even with the racy
implementation. */
#define REPEAT_COUNT 1000
#include "glthread/thread.h"
#include "glthread/yield.h"
#include "macros.h"
#if EXPLICIT_YIELD
# define yield() gl_thread_yield ()
#else
# define yield()
#endif
/* Counters for each thread. */
static unsigned int counter[THREAD_COUNT][5];
/* A variable of type 'unsigned int'. */
static unsigned int int_variable;
static void *
int_mutator_thread (void *arg)
{
int *pcounter = (int *) arg;
int repeat;
for (repeat = REPEAT_COUNT; repeat > 0; repeat--)
{
if (atomic_compare_and_swap (&int_variable, 0, 10) == 0)
pcounter[0]++;
yield ();
if (atomic_compare_and_swap (&int_variable, 14, 17) == 14)
pcounter[1]++;
yield ();
if (atomic_compare_and_swap (&int_variable, 20, 0) == 20)
pcounter[2]++;
yield ();
if (atomic_compare_and_swap (&int_variable, 10, 14) == 10)
pcounter[3]++;
yield ();
if (atomic_compare_and_swap (&int_variable, 17, 20) == 17)
pcounter[4]++;
yield ();
}
return NULL;
}
/* A variable of type 'uintptr_t'. */
static uintptr_t ptr_variable;
static void *
ptr_mutator_thread (void *arg)
{
int *pcounter = (int *) arg;
int repeat;
for (repeat = REPEAT_COUNT; repeat > 0; repeat--)
{
if (atomic_compare_and_swap_ptr (&ptr_variable, 0, 10) == 0)
pcounter[0]++;
yield ();
if (atomic_compare_and_swap_ptr (&ptr_variable, 14, 17) == 14)
pcounter[1]++;
yield ();
if (atomic_compare_and_swap_ptr (&ptr_variable, 20, 0) == 20)
pcounter[2]++;
yield ();
if (atomic_compare_and_swap_ptr (&ptr_variable, 10, 14) == 10)
pcounter[3]++;
yield ();
if (atomic_compare_and_swap_ptr (&ptr_variable, 17, 20) == 17)
pcounter[4]++;
yield ();
}
return NULL;
}
int
main ()
{
/* Check simple uses of atomic_compare_and_swap. */
{
unsigned int x[3] = { 0xDEADBEEFU, 11, 0xDEADBEEFU };
ASSERT (atomic_compare_and_swap (&x[1], 0, 17) == 11);
ASSERT (x[1] == 11);
ASSERT (atomic_compare_and_swap (&x[1], 4, 11) == 11);
ASSERT (x[1] == 11);
ASSERT (atomic_compare_and_swap (&x[1], 11, 15) == 11);
ASSERT (x[1] == 15);
ASSERT (x[0] == 0xDEADBEEFU);
ASSERT (x[2] == 0xDEADBEEFU);
}
/* Check simple uses of atomic_compare_and_swap_ptr. */
{
uintptr_t v1 = ~(uintptr_t)0 / 3;
uintptr_t v2 = ~(uintptr_t)0 / 5 * 4;
uintptr_t v3 = ~(uintptr_t)0 / 7 * 3;
uintptr_t x[3] = { 0xDEADBEEFU, v1, 0xDEADBEEFU };
ASSERT (atomic_compare_and_swap_ptr (&x[1], 0, v3) == v1);
ASSERT (x[1] == v1);
ASSERT (atomic_compare_and_swap_ptr (&x[1], 4, v1) == v1);
ASSERT (x[1] == v1);
ASSERT (atomic_compare_and_swap_ptr (&x[1], v1, v2) == v1);
ASSERT (x[1] == v2);
ASSERT (x[0] == 0xDEADBEEFU);
ASSERT (x[2] == 0xDEADBEEFU);
}
/* Check atomicity of atomic_compare_and_swap. */
{
void * (*funcs[2]) (void *) = { int_mutator_thread, ptr_mutator_thread };
int f;
for (f = 0; f < 2; f++)
{
void * (*func) (void *) = funcs[f];
int i, j;
gl_thread_t threads[THREAD_COUNT];
/* Initialization. */
for (i = 0; i < THREAD_COUNT; i++)
for (j = 0; j < 5; j++)
counter[i][j] = 0;
/* Spawn the threads. */
for (i = 0; i < THREAD_COUNT; i++)
threads[i] = gl_thread_create (func, &counter[i][0]);
/* Wait for the threads to terminate. */
for (i = 0; i < THREAD_COUNT; i++)
gl_thread_join (threads[i], NULL);
/* Sum up the work that the threads have done. */
unsigned int sum[5];
for (j = 0; j < 5; j++)
{
sum[j] = 0;
for (i = 0; i < THREAD_COUNT; i++)
sum[j] += counter[i][j];
}
/* If things went atomically, the threads have moved the variable's
value through the cycle 0 -> 10 -> 14 -> 17 -> 20 -> 0 ... a large
number of times.
sum[0] is the number of transitions 0 -> 10.
sum[3] is the number of transitions 10 -> 14.
sum[1] is the number of transitions 14 -> 17.
sum[4] is the number of transitions 17 -> 20.
sum[2] is the number of transitions 20 -> 0.
Since the cycle started at 0 and ends anywhere (namely, when all
threads when through their loop REPEAT_COUNT times), the sequence
sum[0], sum[3], sum[1], sum[4], sum[2], sum[0] - 1
must be monotonically decreasing.
If things did not go atomically, the counters don't exhibit this
pattern. */
printf ("Counters: %u %u %u %u %u\n",
sum[0], sum[3], sum[1], sum[4], sum[2]);
ASSERT ((sum[0] == sum[3] || sum[0] == sum[3] + 1)
&& (sum[3] == sum[1] || sum[3] == sum[1] + 1)
&& (sum[1] == sum[4] || sum[1] == sum[4] + 1)
&& (sum[4] == sum[2] || sum[4] == sum[2] + 1)
&& (sum[2] + 1 == sum[0] || sum[2] == sum[0]));
}
}
return 0;
}
#else
/* No multithreading available. */
#include
int
main ()
{
fputs ("Skipping test: multithreading not enabled\n", stderr);
return 77;
}
#endif