mutex_common.h 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /*
  2. * Copyright (C) 2023 Amazon.com Inc. or its affiliates. All rights reserved.
  3. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. */
  5. #ifndef MUTEX_COMMON_H
  6. #define MUTEX_COMMON_H
  7. #include <pthread.h>
  8. #include <stdio.h>
  9. #include <assert.h>
  10. #include <errno.h>
  11. #include <unistd.h>
  12. #include <stdbool.h>
  13. #include <time.h>
  14. #include <stdlib.h>
  15. #include <string.h>
  16. enum Constants {
  17. NUM_ITER = 250000,
  18. NUM_THREADS = 12,
  19. NUM_RETRY = 8,
  20. RETRY_SLEEP_TIME_US = 1000,
  21. };
  22. // We're counting how many times each thread was called using this array
  23. // Main thread is also counted here so we need to make arrays bigger
  24. typedef struct {
  25. int tids[NUM_THREADS + 1];
  26. int calls[NUM_THREADS + 1];
  27. } StatCollector;
  28. typedef struct {
  29. pthread_mutex_t *mutex;
  30. StatCollector stat;
  31. int counter;
  32. bool is_sleeping;
  33. } MutexCounter;
  34. // This enum defines whether thread should sleep to increase contention
  35. enum SleepState {
  36. NON_SLEEP = 0,
  37. SLEEP = 1,
  38. };
  39. void
  40. mutex_counter_init(MutexCounter *mutex_counter, pthread_mutex_t *mutex,
  41. enum SleepState is_sleeping)
  42. {
  43. memset(mutex_counter, 0, sizeof(*mutex_counter));
  44. mutex_counter->mutex = mutex;
  45. mutex_counter->is_sleeping = is_sleeping;
  46. }
  47. // This function spawns the thread using exponential retries if it receives
  48. // EAGAIN
  49. static inline void
  50. spawn_thread(pthread_t *tid, void *func, void *arg)
  51. {
  52. int status_code = -1;
  53. int timeout_us = RETRY_SLEEP_TIME_US;
  54. for (int tries = 0; status_code != 0 && tries < NUM_RETRY; ++tries) {
  55. status_code = pthread_create(tid, NULL, (void *(*)(void *))func, arg);
  56. assert(status_code == 0 || status_code == EAGAIN);
  57. if (status_code == EAGAIN) {
  58. usleep(timeout_us);
  59. timeout_us *= 2;
  60. }
  61. }
  62. assert(status_code == 0 && "Thread creation should succeed");
  63. }
  64. // This function adds tid to our stat
  65. static inline void
  66. add_to_stat(StatCollector *stat, int tid)
  67. {
  68. int tid_num = 0;
  69. for (; tid_num < NUM_THREADS + 1 && stat->tids[tid_num] != 0; ++tid_num) {
  70. if (stat->tids[tid_num] == tid) {
  71. stat->calls[tid_num]++;
  72. return;
  73. }
  74. }
  75. assert(tid_num < NUM_THREADS + 1);
  76. stat->tids[tid_num] = tid;
  77. stat->calls[tid_num] = 1;
  78. }
  79. // This function prints number of calls by TID
  80. static inline void
  81. print_stat(StatCollector *stat)
  82. {
  83. fprintf(stderr, "Thread calls count by TID\n");
  84. for (int i = 0; i < NUM_THREADS + 1; ++i) {
  85. if (stat->tids[i] != 0) {
  86. fprintf(stderr, "TID: %d; Calls: %d\n", stat->tids[i],
  87. stat->calls[i]);
  88. }
  89. }
  90. }
  91. // This function is run by the threads, it increases counter in a loop and then
  92. // sleeps after unlocking the mutex to provide better contention
  93. static inline void *
  94. inc_shared_variable(void *arg)
  95. {
  96. MutexCounter *mutex_counter = (MutexCounter *)(arg);
  97. int sleep_us = 0;
  98. while (!pthread_mutex_lock(mutex_counter->mutex)
  99. && mutex_counter->counter < NUM_ITER) {
  100. mutex_counter->counter++;
  101. add_to_stat(&mutex_counter->stat, (int)(pthread_self()));
  102. if (mutex_counter->is_sleeping) {
  103. sleep_us = rand() % 1000;
  104. }
  105. assert(pthread_mutex_unlock(mutex_counter->mutex) == 0
  106. && "Should be able to unlock a mutex");
  107. if (mutex_counter->is_sleeping) {
  108. usleep(sleep_us);
  109. }
  110. }
  111. assert(mutex_counter->counter == NUM_ITER);
  112. assert(pthread_mutex_unlock(mutex_counter->mutex) == 0
  113. && "Should be able to unlock the mutex after test execution");
  114. return NULL;
  115. }
  116. // Locking and unlocking a mutex in a single thread.
  117. static inline void *
  118. same_thread_lock_unlock_test(void *mutex)
  119. {
  120. for (int i = 0; i < NUM_ITER; ++i) {
  121. assert(pthread_mutex_lock(mutex) == 0
  122. && "Main thread should be able to lock a mutex");
  123. assert(pthread_mutex_unlock(mutex) == 0
  124. && "Main thread should be able to unlock a mutex");
  125. }
  126. return NULL;
  127. }
  128. // This function spawns a thread that locks and unlocks a mutex `NUM_ITER` times
  129. // in a row
  130. static inline void
  131. same_non_main_thread_lock_unlock_test(pthread_mutex_t *mutex)
  132. {
  133. pthread_t tid = 0;
  134. spawn_thread(&tid, same_thread_lock_unlock_test, mutex);
  135. assert(tid != 0 && "TID can't be 0 after successful thread creation");
  136. assert(pthread_join(tid, NULL) == 0
  137. && "Thread should be joined successfully");
  138. }
  139. // This function checks basic contention between main and non-main thread
  140. // increasing the shared variable
  141. static inline void
  142. two_threads_inc_test(pthread_mutex_t *mutex)
  143. {
  144. MutexCounter mutex_counter;
  145. mutex_counter_init(&mutex_counter, mutex, false);
  146. pthread_t tid = 0;
  147. spawn_thread(&tid, inc_shared_variable, &mutex_counter);
  148. assert(tid != 0 && "TID can't be 0 after successful thread creation");
  149. inc_shared_variable(&mutex_counter);
  150. assert(pthread_join(tid, NULL) == 0
  151. && "Thread should be joined without errors");
  152. assert(mutex_counter.counter == NUM_ITER);
  153. }
  154. // This function creates number of threads specified by NUM_THREADS and run
  155. // concurrent increasing of shared variable
  156. static inline void
  157. max_threads_inc_test(pthread_mutex_t *mutex, int threads_num,
  158. enum SleepState is_sleeping)
  159. {
  160. MutexCounter mutex_counter;
  161. mutex_counter_init(&mutex_counter, mutex, is_sleeping);
  162. pthread_t tids[threads_num];
  163. for (int i = 0; i < threads_num; ++i) {
  164. spawn_thread(&tids[i], inc_shared_variable, &mutex_counter);
  165. }
  166. inc_shared_variable(&mutex_counter);
  167. for (int i = 0; i < threads_num; ++i) {
  168. assert(pthread_join(tids[i], NULL) == 0
  169. && "Thread should be joined without errors");
  170. }
  171. print_stat(&mutex_counter.stat);
  172. }
  173. // This function just runs all the tests described above
  174. static inline void
  175. run_common_tests(pthread_mutex_t *mutex)
  176. {
  177. srand(time(NULL));
  178. fprintf(stderr, "Starting same_thread_lock_unlock_test test\n");
  179. same_thread_lock_unlock_test(mutex);
  180. fprintf(stderr, "Finished same_thread_lock_unlock_test test\n");
  181. fprintf(stderr, "Starting same_non_main_thread_lock_unlock_test test\n");
  182. same_non_main_thread_lock_unlock_test(mutex);
  183. fprintf(stderr, "Finished same_non_main_thread_lock_unlock_test test\n");
  184. fprintf(stderr, "Starting two_threads_inc_test test\n");
  185. two_threads_inc_test(mutex);
  186. fprintf(stderr, "Finished two_threads_inc_test test\n");
  187. fprintf(stderr, "Starting max_threads_inc_test_sleep test\n");
  188. max_threads_inc_test(mutex, NUM_THREADS, SLEEP);
  189. fprintf(stderr, "Finished concurrent_inc sleep test\n");
  190. fprintf(stderr, "Starting max_threads_inc_test_non_sleep test\n");
  191. max_threads_inc_test(mutex, NUM_THREADS, NON_SLEEP);
  192. fprintf(stderr, "Finished max_threads_inc_test test\n");
  193. }
  194. #endif // MUTEX_COMMON_H