#define _POSIX_C_SOURCE 200809L
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
static long counter;
static atomic_long acounter;
static int arrived;
static const long iterations = 100000;
static const char *mode;
static void check(int e) {
    if (e) { fprintf(stderr, "pthread: %s\n", strerror(e)); exit(EXIT_FAILURE); }
}
static void *worker(void *unused) {
    (void)unused;
    if (strcmp(mode, "split") == 0) {
        long old = atomic_load(&acounter);
        /* Force both atomic loads before either atomic store. */
        check(pthread_mutex_lock(&m));
        arrived++;
        if (arrived == 2) check(pthread_cond_broadcast(&cv));
        while (arrived < 2) check(pthread_cond_wait(&cv, &m));
        check(pthread_mutex_unlock(&m));
        atomic_store(&acounter, old + 1);
    } else {
        for (long i = 0; i < iterations; i++) {
            if (strcmp(mode, "mutex") == 0) {
                check(pthread_mutex_lock(&m));
                counter++;
                check(pthread_mutex_unlock(&m));
            } else {
                atomic_fetch_add(&acounter, 1);
            }
        }
    }
    return NULL;
}
int main(int argc, char **argv) {
    if (argc != 2 || (strcmp(argv[1], "mutex") && strcmp(argv[1], "atomic") && strcmp(argv[1], "split"))) {
        fprintf(stderr, "Usage: %s mutex|atomic|split\n", argv[0]); return EXIT_FAILURE;
    }
    mode = argv[1]; atomic_init(&acounter, 0);
    pthread_t t[2];
    for (int i = 0; i < 2; i++) check(pthread_create(&t[i], NULL, worker, NULL));
    for (int i = 0; i < 2; i++) check(pthread_join(t[i], NULL));
    long actual = strcmp(mode, "mutex") == 0 ? counter : atomic_load(&acounter);
    long expected = strcmp(mode, "split") == 0 ? 1 : 2 * iterations;
    printf("mode=%s actual=%ld expected=%ld\n", mode, actual, expected);
    check(pthread_cond_destroy(&cv)); check(pthread_mutex_destroy(&m));
    return actual == expected ? EXIT_SUCCESS : EXIT_FAILURE;
}
