Last active
April 30, 2019 14:35
-
-
Save wjlroe/a9e0949a784b96a8afdfecad79090c7d to your computer and use it in GitHub Desktop.
Playing with C11's stdatomics like atomic_compare_exchange and atomic_fetch_add for thread-safe global counters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdatomic.h> | |
#include <string.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <pthread.h> | |
#include <unistd.h> | |
#define NUM_THREADS 1000 | |
pthread_t threads[NUM_THREADS]; | |
#define INC_TIMES 500 | |
int global_counter = 0; | |
_Atomic int atomic_global_counter = 0; | |
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; | |
void* run_naive(void *arg) { | |
for (int i = 0; i < INC_TIMES; i++) { | |
global_counter++; | |
} | |
} | |
void* run_mutex(void *arg) { | |
for (int i = 0; i < INC_TIMES; i++) { | |
pthread_mutex_lock(&mutex); | |
global_counter++; | |
pthread_mutex_unlock(&mutex); | |
} | |
} | |
void* run_cas(void *arg) { | |
int old, new; | |
for (int i = 0; i < INC_TIMES; i++) { | |
do { | |
old = atomic_global_counter; | |
new = atomic_global_counter + 1; | |
} while(!atomic_compare_exchange_strong(&atomic_global_counter, &old, new)); | |
} | |
} | |
void* run_atomic_add(void *arg) { | |
for (int i = 0; i < INC_TIMES; i++) { | |
atomic_fetch_add(&atomic_global_counter, 1); | |
} | |
} | |
double run_with_threads(void * (run_method)(void *)) { | |
global_counter = 0; | |
atomic_global_counter = 0; | |
clock_t start, end; | |
double cpu_time_used; | |
start = clock(); | |
for (int i = 0; i < NUM_THREADS; i++) { | |
int err = pthread_create(&(threads[i]), NULL, run_method, NULL); | |
if (err != 0) { | |
printf("\nThread creation failed: %s", strerror(err)); | |
} | |
} | |
for (int i = 0; i < NUM_THREADS; i++) { | |
pthread_join(threads[i], NULL); | |
} | |
end = clock(); | |
cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC; | |
return cpu_time_used; | |
} | |
int main() { | |
int desired_value = NUM_THREADS * INC_TIMES; | |
double naive_time = run_with_threads(&run_naive); | |
if (global_counter != desired_value) { | |
printf("!!! [Naive version]Counter (%d) does not equal expected value (%d)\n", global_counter, desired_value); | |
} else { | |
printf("Naive version worked\n"); | |
} | |
double mutex_locked_time = run_with_threads(&run_mutex); | |
if (global_counter != desired_value) { | |
printf("!!! [Mutex version]Counter (%d) does not equal expected value (%d)\n", global_counter, desired_value); | |
} else { | |
printf("Mutex version worked\n"); | |
} | |
double cas_time = run_with_threads(&run_cas); | |
global_counter = atomic_global_counter; | |
if (global_counter != desired_value) { | |
printf("!!! [CAS version]Counter (%d) does not equal expected value (%d)\n", global_counter, desired_value); | |
} else { | |
printf("CAS version worked\n"); | |
} | |
double atomic_add_time = run_with_threads(&run_atomic_add); | |
global_counter = atomic_global_counter; | |
if (global_counter != desired_value) { | |
printf("!!! [Atomic_fetch_add version]Counter (%d) does not equal expected value (%d)\n", global_counter, desired_value); | |
} else { | |
printf("Atomic_fetch_add version worked\n"); | |
} | |
printf("\n"); | |
printf("Naive: %f\n", naive_time); | |
printf("Mutex: %f\n", mutex_locked_time); | |
printf("CAS: %f\n", cas_time); | |
printf("Atomic_fetch_add: %f\n", atomic_add_time); | |
printf("\n"); | |
printf("Done\n"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Representative output: