D Multi-Threading in C

D.1 The AD Server that loses Money

#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <time.h>

#define handle_error_en(en, msg) \
    do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while (0)


int ghc = 0;
int num_threads = 0;

struct server_thread_info {
    pthread_t t_id;
    int lhc;
};

static void* increment_hc(void *t){
        struct server_thread_info* t_server = t;
    t_server->lhc = 0;
    
    int hits = rand()%1000000 + 100000;
    for(int i = 0; i < hits; i++){
        t_server->lhc++;
        ghc++;
    }

        printf("Thread %d incremented the global hit counter %d times.\n", t_server->t_id, t_server->lhc);
        return t;
}



int main(void) {
    
    int s;
        
    printf("Number of threads for the ad server? ");
    //Get number of threads from stdin
        scanf("%d", &num_threads);
        
        //Create thread information data structure
        struct server_thread_info *t_info;
        t_info = calloc(num_threads, sizeof(struct server_thread_info));        
    if(t_info == NULL) handle_error("calloc");

    //Create and run threads
    for(int t=0; t<num_threads; t++){
        
        //Initialize local hit counter to 0.
        t_info[t].lhc = 0;
        
        // Each thread calls increment_hc() on startup
        s = pthread_create(&t_info[t].t_id, NULL, &increment_hc, &t_info[t]);
        if(s!= 0)handle_error_en(s, "pthread_create");
    }

    //Cleanup all threads and check hit counter count
    int real_ghc = 0;
    for(int t=0;t<num_threads; t++){
        s = pthread_join(t_info[t].t_id, NULL);
        if(s!=0) handle_error_en(s, "pthread_join");

        real_ghc += t_info[t].lhc;

    }
    printf("The global hit counter is %d and it should be %d\n", ghc, real_ghc);
    printf("You lost %f Dirhams!\n", (real_ghc - ghc) / 100.0 / 100.0);
    return EXIT_SUCCESS;
}

Now that you see the issues with lack of sychronization. Introduce spin locks to fix the problem:

First create a global lock for access to the global hit counter:

#include <stdatomic.h>

atomic_flag ghc_busy = ATOMIC_FLAG_INIT; 

Then, identify the critical section and place the locks around it.

static void* increment_hc(void *t){
        struct server_thread_info* t_server = t;
          t_server->lhc = 0;
    
    int hits = rand()%1000000 + 100000;
    for(int i = 0; i < hits; i++){
      t_server->lhc++;
    while(atomic_flag_test_and_set(&ghc_busy));//Spin - Note the ; at the end
      ghc++; //Update counter
      atomic_flag_clear(&ghc_busy);//Release lock   
  }
  printf("Thread %d incremented the global hit counter %d times.\n", 
        t_server->t_id, t_server->lhc);
  return t;
}

Now replace the spin lock with a semaphore/mutex instead!