>From 43e5eca63a07d4119b104c4f3cb2aefe3dc9f2ea Mon Sep 17 00:00:00 2001 From: Bruno Haible Date: Mon, 15 Feb 2021 04:29:18 +0100 Subject: [PATCH 2/2] simple-atomic: Add tests. * tests/test-simple-atomic.c: New file. * modules/simple-atomic-tests: New file. --- ChangeLog | 4 + modules/simple-atomic-tests | 14 +++ tests/test-simple-atomic.c | 234 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 modules/simple-atomic-tests create mode 100644 tests/test-simple-atomic.c diff --git a/ChangeLog b/ChangeLog index 5d7337c..29984bc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2021-02-14 Bruno Haible + simple-atomic: Add tests. + * tests/test-simple-atomic.c: New file. + * modules/simple-atomic-tests: New file. + simple-atomic: New module. * lib/simple-atomic.h: New file. * lib/simple-atomic.c: New file, based on lib/asyncsafe-spin.c. diff --git a/modules/simple-atomic-tests b/modules/simple-atomic-tests new file mode 100644 index 0000000..a57b9fc --- /dev/null +++ b/modules/simple-atomic-tests @@ -0,0 +1,14 @@ +Files: +tests/test-simple-atomic.c +tests/macros.h + +Depends-on: +thread +yield + +configure.ac: + +Makefile.am: +TESTS += test-simple-atomic +check_PROGRAMS += test-simple-atomic +test_simple_atomic_LDADD = $(LDADD) @LIBMULTITHREAD@ @YIELD_LIB@ diff --git a/tests/test-simple-atomic.c b/tests/test-simple-atomic.c new file mode 100644 index 0000000..04baf05 --- /dev/null +++ b/tests/test-simple-atomic.c @@ -0,0 +1,234 @@ +/* Test of simple atomic operations for multithreading. + Copyright (C) 2021 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 + +#if EXPLICIT_YIELD +# define yield() gl_thread_yield () +#else +# define yield() +#endif + +#include "glthread/thread.h" +#include "glthread/yield.h" + +#include "macros.h" + +/* 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 -- 2.7.4