├── .gitignore ├── 00-inc-not-thread-safe.c ├── 01-royal-chef-unhappy.c ├── 02-royal-chef-happy.c ├── 03-simple-stack.c ├── 04-non-concurrent-stack.c ├── 05-concurrent-stack.c ├── 06-bounded-cstack.c └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | .vscode -------------------------------------------------------------------------------- /00-inc-not-thread-safe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define THREAD_COUNT 1000 5 | 6 | int count = 0; 7 | 8 | // increment s the `count` variable by `THREAD_COUNT` 9 | // one at a time 10 | void * inc_count() { 11 | for (int i = 0; i < THREAD_COUNT; i++) { 12 | count ++; 13 | } 14 | return NULL; 15 | } 16 | 17 | // resets to a base 0 setup 18 | void reset() { 19 | count = 0; 20 | } 21 | 22 | 23 | // test starts the THREAD_COUNT threads 24 | // each increments the global count variable 25 | // and would wait for all threads to complete 26 | // the function would print the expected and observed values 27 | void test() { 28 | pthread_t th[THREAD_COUNT]; 29 | 30 | reset(); 31 | 32 | for (int i = 0; i < THREAD_COUNT; i++) { 33 | if (pthread_create(&th[i], NULL, &inc_count, NULL) != 0) { 34 | perror("failed to create thread"); 35 | } 36 | } 37 | 38 | for (int i = 0; i < THREAD_COUNT; i++) { 39 | if (pthread_join(th[i], NULL) != 0) { 40 | perror("failed to join thread"); 41 | } 42 | } 43 | 44 | printf("expected %d, observed %d\n", THREAD_COUNT * THREAD_COUNT, count); 45 | } 46 | 47 | int main(int argc, char *argv[]) { 48 | for (int i = 0; i < 10; i++) { 49 | test(); 50 | } 51 | return 0; 52 | } -------------------------------------------------------------------------------- /01-royal-chef-unhappy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // Activities of the Chef and the King 7 | typedef enum { 8 | CHEF_WAKES_UP, 9 | CHEF_PREPARATION_START, 10 | CHEF_PREPARATION_END, 11 | 12 | KING_WAKES_UP, 13 | KING_ADDRESSES_PUBLIC, 14 | KING_VISITS_DINING_AREA, 15 | KING_EATS_LUNCH, 16 | KING_ANGRY, 17 | } activity_t; 18 | 19 | 20 | // represents if the lunch is ready or not 21 | int is_prepared = 0; 22 | 23 | // random delay while doing an activity 24 | void act(activity_t activity) { 25 | usleep(50); 26 | } 27 | 28 | // chef's routine 29 | void * routine_chef() { 30 | act(CHEF_WAKES_UP); 31 | act(CHEF_PREPARATION_START); 32 | act(CHEF_PREPARATION_END); 33 | 34 | is_prepared = 1; 35 | return NULL; 36 | } 37 | 38 | 39 | // king's routine 40 | void * routine_king() { 41 | act(KING_WAKES_UP); 42 | act(KING_ADDRESSES_PUBLIC); 43 | act(KING_VISITS_DINING_AREA); 44 | 45 | // if king sees that the food is not prepared 46 | // after visiting the dining area 47 | // the king gets angry 48 | if (!is_prepared) { 49 | act(KING_ANGRY); 50 | return (void *) KING_ANGRY; 51 | } 52 | 53 | // otherwise the king eats the lunch 54 | act(KING_EATS_LUNCH); 55 | return (void *) KING_EATS_LUNCH; 56 | } 57 | 58 | 59 | // resets everything 60 | void reset() { 61 | is_prepared = 0; 62 | srand(time(NULL)); 63 | } 64 | 65 | void test() { 66 | pthread_t th_king, th_chef; 67 | 68 | reset(); 69 | 70 | // start the chef's routine 71 | if (pthread_create(&th_chef, NULL, &routine_chef, NULL) != 0) { 72 | perror("failed to create the chef thread"); 73 | } 74 | 75 | // start the king's routine 76 | if (pthread_create(&th_king, NULL, &routine_king, NULL) != 0) { 77 | perror("failed to create the king thread"); 78 | } 79 | 80 | // wait for the threads to complete 81 | if (pthread_join(th_chef, NULL) != 0) { 82 | perror("failed to join on chef thread"); 83 | } 84 | 85 | // check the return value of king's routine 86 | // and see if he was angry or not 87 | activity_t state_king; 88 | if (pthread_join(th_king, (void *) &state_king) != 0) { 89 | perror("failed to join on king thread"); 90 | } 91 | 92 | // print if king was angry 93 | printf("is king angry: %d\n", state_king == KING_ANGRY); 94 | } 95 | 96 | int main(int argc, char *argv[]) { 97 | for (int i = 0; i < 10; i++) { 98 | test(); 99 | } 100 | return 0; 101 | } -------------------------------------------------------------------------------- /02-royal-chef-happy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Activities of the Chef and the King 8 | typedef enum { 9 | CHEF_WAKES_UP, 10 | CHEF_PREPARATION_START, 11 | CHEF_PREPARATION_END, 12 | 13 | KING_WAKES_UP, 14 | KING_ADDRESSES_PUBLIC, 15 | KING_VISITS_DINING_AREA, 16 | KING_EATS_LUNCH, 17 | KING_ANGRY, 18 | } activity_t; 19 | 20 | // represents if the lunch is ready or not 21 | int is_prepared = 0; 22 | 23 | // semaphore to block and signal 24 | sem_t sem_chef; 25 | 26 | // random delay while doing an activity 27 | void act(activity_t activity) { 28 | usleep(50); 29 | } 30 | 31 | // chef's routine 32 | void * routine_chef() { 33 | act(CHEF_WAKES_UP); 34 | act(CHEF_PREPARATION_START); 35 | act(CHEF_PREPARATION_END); 36 | 37 | is_prepared = 1; 38 | 39 | // signals the king once the food is ready 40 | sem_post(&sem_chef); 41 | 42 | return NULL; 43 | } 44 | 45 | // king's routine 46 | void * routine_king() { 47 | act(KING_WAKES_UP); 48 | act(KING_ADDRESSES_PUBLIC); 49 | act(KING_VISITS_DINING_AREA); 50 | 51 | // king is waiting for the food is ready 52 | sem_wait(&sem_chef); 53 | 54 | // if king sees that the food is not prepared 55 | // after visiting the dining area 56 | // the king gets angry 57 | if (!is_prepared) { 58 | act(KING_ANGRY); 59 | return (void *) KING_ANGRY; 60 | } 61 | 62 | // otherwise the king eats the lunch 63 | act(KING_EATS_LUNCH); 64 | return (void *) KING_EATS_LUNCH; 65 | } 66 | 67 | // resets everything 68 | void reset() { 69 | is_prepared = 0; 70 | } 71 | 72 | void test() { 73 | pthread_t th_king, th_chef; 74 | 75 | // initialize the semaphore to 0 76 | // everyone waits until someone signals 77 | sem_init(&sem_chef, 0, 0); 78 | 79 | reset(); 80 | 81 | // start the chef's routine 82 | if (pthread_create(&th_chef, NULL, &routine_chef, NULL) != 0) { 83 | perror("failed to create the chef thread"); 84 | } 85 | 86 | // start the king's routine 87 | if (pthread_create(&th_king, NULL, &routine_king, NULL) != 0) { 88 | perror("failed to create the king thread"); 89 | } 90 | 91 | // wait for the threads to complete 92 | if (pthread_join(th_chef, NULL) != 0) { 93 | perror("failed to join on chef thread"); 94 | } 95 | 96 | // check the return value of king's routine 97 | // and see if he was angry or not 98 | activity_t state_king; 99 | if (pthread_join(th_king, (void *) &state_king) != 0) { 100 | perror("failed to join on king thread"); 101 | } 102 | 103 | // print if king was angry 104 | printf("is king angry: %d\n", state_king == KING_ANGRY); 105 | 106 | sem_destroy(&sem_chef); 107 | } 108 | 109 | int main(int argc, char *argv[]) { 110 | for (int i = 0; i < 10; i++) { 111 | test(); 112 | } 113 | return 0; 114 | } -------------------------------------------------------------------------------- /03-simple-stack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // concurrent stack to hold int values 7 | struct cstack { 8 | int len; 9 | int capacity; 10 | int *array; 11 | }; 12 | typedef struct cstack cstack; 13 | 14 | // creating a new instance of concurrent stack 15 | cstack * cs_new(int capacity) { 16 | // allocating the stack 17 | cstack *s = (cstack *) malloc(1 * sizeof(cstack)); 18 | s -> len = 0; 19 | s -> capacity = capacity; 20 | s -> array = (int *) malloc(capacity * sizeof(int)); 21 | return s; 22 | } 23 | 24 | // destroying and marking the malloc'ed objects free for GC 25 | void cs_destroy(cstack *s) { 26 | free(s -> array); 27 | free(s); 28 | } 29 | 30 | // push `x` on the stack 31 | void cs_push(cstack *s, int x) { 32 | s -> array[(s -> len)++] = x; 33 | } 34 | 35 | // pops the last element from the stack 36 | int cs_pop(cstack *s) { 37 | return s -> array[--(s -> len)]; 38 | } 39 | 40 | int main(int argc, char *argv[]) { 41 | cstack *s = cs_new(10); 42 | 43 | for (int i = 0; i < 10; i++) { 44 | cs_push(s, i); 45 | } 46 | 47 | for (int i = 0; i < 10; i++) { 48 | printf("%d\n", cs_pop(s)); 49 | } 50 | 51 | cs_destroy(s); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /04-non-concurrent-stack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define THREAD_COUNT 1000 7 | 8 | // concurrent stack to hold int values 9 | struct cstack { 10 | int len; 11 | int capacity; 12 | int *array; 13 | }; 14 | typedef struct cstack cstack; 15 | 16 | // creating a new instance of concurrent stack 17 | cstack * cs_new(int capacity) { 18 | // allocating the stack 19 | cstack *s = (cstack *) malloc(1 * sizeof(cstack)); 20 | s -> len = 0; 21 | s -> capacity = capacity; 22 | s -> array = (int *) malloc(capacity * sizeof(int)); 23 | return s; 24 | } 25 | 26 | // destroying and marking the malloc'ed objects free for GC 27 | void cs_destroy(cstack *s) { 28 | free(s -> array); 29 | free(s); 30 | } 31 | 32 | // push `x` on the stack 33 | void cs_push(cstack *s, int x) { 34 | s -> array[(s -> len)++] = x; 35 | } 36 | 37 | // pops the last element from the stack 38 | int cs_pop(cstack *s) { 39 | return s -> array[--(s -> len)]; 40 | } 41 | 42 | // struct to pass an argument to the thread 43 | // at the beginning of the execution 44 | struct arg_cstack_push { 45 | cstack * s; 46 | int x; 47 | }; 48 | typedef struct arg_cstack_push arg_cstack_push; 49 | 50 | void * push_through_thread(void * _arg) { 51 | arg_cstack_push *arg = (arg_cstack_push *) _arg; 52 | cs_push(arg->s, arg->x); 53 | return NULL; 54 | } 55 | 56 | void test() { 57 | cstack *s = cs_new(THREAD_COUNT); 58 | pthread_t *threads = (pthread_t *) malloc(THREAD_COUNT * sizeof(pthread_t)); 59 | 60 | // creating the threads to push elements on the stack 61 | for (int i = 0; i < THREAD_COUNT; i++) { 62 | arg_cstack_push arg = {s, i}; 63 | if (pthread_create(&threads[i], NULL, push_through_thread, &arg) != 0) { 64 | perror("error creating a producer thread"); 65 | return; 66 | } 67 | } 68 | 69 | // wait for all the threads to complete 70 | for (int i = 0; i < THREAD_COUNT; i++) { 71 | if (pthread_join(threads[i], NULL)) { 72 | perror("unable to wait for the thread to complete"); 73 | return; 74 | } 75 | } 76 | 77 | printf("entries in stack: expected %d, observed %d\n", THREAD_COUNT, s->len); 78 | 79 | free(threads); 80 | cs_destroy(s); 81 | } 82 | 83 | int main(int argc, char *argv[]) { 84 | for (int i = 0; i < 25; i++) { 85 | test(); 86 | } 87 | return 0; 88 | } 89 | -------------------------------------------------------------------------------- /05-concurrent-stack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define THREAD_COUNT 1000 7 | 8 | // concurrent stack to hold int values 9 | struct cstack { 10 | int len; 11 | int capacity; 12 | int *array; 13 | sem_t mutex; 14 | }; 15 | typedef struct cstack cstack; 16 | 17 | // creating a new instance of concurrent stack 18 | cstack * cs_new(int capacity) { 19 | // allocating the stack 20 | cstack *s = (cstack *) malloc(1 * sizeof(cstack)); 21 | s -> len = 0; 22 | s -> capacity = capacity; 23 | s -> array = (int *) malloc(capacity * sizeof(int)); 24 | sem_init(&s -> mutex, 0, 1); 25 | return s; 26 | } 27 | 28 | // destroying and marking the malloc'ed objects free for GC 29 | void cs_destroy(cstack *s) { 30 | sem_destroy(&(s -> mutex)); 31 | free(s -> array); 32 | free(s); 33 | } 34 | 35 | // push `x` on the stack 36 | void cs_push(cstack *s, int x) { 37 | // wait for the turn 38 | sem_wait(&(s -> mutex)); 39 | 40 | // one thread to enter and insert into the stack 41 | s -> array[(s -> len)++] = x; 42 | 43 | // increase the counter allowing some other thread to enter 44 | sem_post(&(s -> mutex)); 45 | } 46 | 47 | // struct to pass an argument to the thread 48 | // at the beginning of the execution 49 | struct arg_cstack_push { 50 | cstack * s; 51 | int x; 52 | }; 53 | typedef struct arg_cstack_push arg_cstack_push; 54 | 55 | void * push_through_thread(void * _arg) { 56 | arg_cstack_push *arg = (arg_cstack_push *) _arg; 57 | cs_push(arg->s, arg->x); 58 | return NULL; 59 | } 60 | 61 | void test() { 62 | cstack *s = cs_new(THREAD_COUNT); 63 | pthread_t *threads = (pthread_t *) malloc(THREAD_COUNT * sizeof(pthread_t)); 64 | 65 | // creating the threads to push elements on the stack 66 | for (int i = 0; i < THREAD_COUNT; i++) { 67 | arg_cstack_push arg = {s, i}; 68 | if (pthread_create(&threads[i], NULL, push_through_thread, &arg) != 0) { 69 | perror("error creating a producer thread"); 70 | return; 71 | } 72 | } 73 | 74 | // wait for all the threads to complete 75 | for (int i = 0; i < THREAD_COUNT; i++) { 76 | if (pthread_join(threads[i], NULL)) { 77 | perror("unable to wait for the thread to complete"); 78 | return; 79 | } 80 | } 81 | 82 | printf("entries in stack: expected %d, observed %d\n", THREAD_COUNT, s->len); 83 | 84 | free(threads); 85 | cs_destroy(s); 86 | } 87 | 88 | int main(int argc, char *argv[]) { 89 | for (int i = 0; i < 25; i++) { 90 | test(); 91 | } 92 | return 0; 93 | } 94 | -------------------------------------------------------------------------------- /06-bounded-cstack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define THREAD_COUNT 1000 7 | 8 | // concurrent stack to hold int values 9 | struct cstack { 10 | int len; 11 | int capacity; 12 | int *array; 13 | sem_t mutex; 14 | sem_t sem_ubound; 15 | sem_t sem_lbound; 16 | }; 17 | typedef struct cstack cstack; 18 | 19 | // creating a new instance of concurrent stack 20 | cstack * cs_new(int capacity) { 21 | // allocating the stack 22 | cstack *s = (cstack *) malloc(1 * sizeof(cstack)); 23 | s -> len = 0; 24 | s -> capacity = capacity; 25 | s -> array = (int *) malloc(capacity * sizeof(int)); 26 | sem_init(&s -> mutex, 0, 1); 27 | sem_init(&s -> sem_ubound, 0, capacity); 28 | sem_init(&s -> sem_lbound, 0, 0); 29 | return s; 30 | } 31 | 32 | // destroying and marking the malloc'ed objects free for GC 33 | void cs_destroy(cstack *s) { 34 | sem_destroy(&(s -> mutex)); 35 | free(s -> array); 36 | free(s); 37 | } 38 | 39 | // push `x` on the stack 40 | void cs_push(cstack *s, int x) { 41 | // allow only `capacity` number of elements in stack 42 | // post that wait 43 | sem_wait(&(s -> sem_ubound)); 44 | 45 | // wait for the turn 46 | sem_wait(&(s -> mutex)); 47 | 48 | // one thread to enter and insert into the stack 49 | s -> array[(s -> len)++] = x; 50 | 51 | // increase the counter allowing some other thread to enter 52 | sem_post(&(s -> mutex)); 53 | 54 | // signal a thread who wants to pop 55 | sem_post(&(s -> sem_lbound)); 56 | } 57 | 58 | // pops the last element from the stack 59 | int cs_pop(cstack *s) { 60 | // wait if there is nothing to pop 61 | sem_wait(&(s -> sem_lbound)); 62 | 63 | // wait for the turn 64 | sem_wait(&(s -> mutex)); 65 | 66 | // one thread to enter and insert into the stack 67 | int x = s -> array[--(s -> len)]; 68 | 69 | // increase the counter allowing some other thread to enter 70 | sem_post(&(s -> mutex)); 71 | 72 | // since an element is popped, we can allow another thread 73 | // to push a new element on the stack 74 | sem_post(&(s -> sem_ubound)); 75 | 76 | return x; 77 | } 78 | 79 | // struct to pass an argument to the thread 80 | // at the beginning of the execution 81 | struct arg_cstack_push { 82 | cstack * s; 83 | int x; 84 | }; 85 | typedef struct arg_cstack_push arg_cstack_push; 86 | 87 | void * push_through_thread(void * _arg) { 88 | arg_cstack_push *arg = (arg_cstack_push *) _arg; 89 | cs_push(arg->s, arg->x); 90 | return NULL; 91 | } 92 | 93 | // struct to pass an argument to the thread 94 | // at the beginning of the execution 95 | struct arg_cstack_pop { 96 | cstack * s; 97 | }; 98 | typedef struct arg_cstack_pop arg_cstack_pop; 99 | 100 | void * pop_through_thread(void * _arg) { 101 | arg_cstack_pop *arg = (arg_cstack_pop *) _arg; 102 | cs_pop(arg->s); 103 | return NULL; 104 | } 105 | 106 | void test() { 107 | cstack *s = cs_new(THREAD_COUNT); 108 | pthread_t *threads = (pthread_t *) malloc(THREAD_COUNT * sizeof(pthread_t)); 109 | 110 | // creating the threads to push elements on the stack 111 | for (int i = 0; i < THREAD_COUNT/2; i++) { 112 | arg_cstack_pop arg = {s}; 113 | if (pthread_create(&threads[i], NULL, pop_through_thread, &arg) != 0) { 114 | perror("error creating a producer thread"); 115 | return; 116 | } 117 | } 118 | 119 | // creating the threads to push elements on the stack 120 | for (int i = THREAD_COUNT/2; i < THREAD_COUNT; i++) { 121 | arg_cstack_push arg = {s, i}; 122 | if (pthread_create(&threads[i], NULL, push_through_thread, &arg) != 0) { 123 | perror("error creating a producer thread"); 124 | return; 125 | } 126 | } 127 | 128 | // wait for all the threads to complete 129 | for (int i = 0; i < THREAD_COUNT; i++) { 130 | if (pthread_join(threads[i], NULL)) { 131 | perror("unable to wait for the thread to complete"); 132 | return; 133 | } 134 | } 135 | 136 | printf("entries in stack: expected %d, observed %d\n", 0, s->len); 137 | 138 | free(threads); 139 | cs_destroy(s); 140 | } 141 | 142 | int main(int argc, char *argv[]) { 143 | for (int i = 0; i < 25; i++) { 144 | test(); 145 | } 146 | return 0; 147 | } 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Concurrency with Semaphores 2 | === 3 | 4 | Concurrency is essential to scale systems, but writing "performant" concurrent code is tricky given the number of things that could go wrong like 5 | 6 | - deadlock 7 | - unnecessary starvation 8 | - inconsistencies 9 | 10 | How, how can we get better? 11 | 12 | You might think, hey! Let me see that JAVA or Golang gives me out of the box. But trust me, that's not enough. 13 | 14 | To be good with concurrency we do not need a particular language, instead we need to solve some super-cool problems 15 | 16 | So, in this series, we go through some super-interesting and fun concurrency problems and build our core understanding of concurrency. 17 | 18 | The entire series is language agnostic, and we can build our solution in literally any programming language that supports Multi-threading. We would not be using any advanced data structures or functions or utilities to solve the problem. 19 | 20 | The only thing we would be using is Semaphores :) 21 | 22 | The entire series is based on one book called "The Little Book of Semaphores" which I read in college and thought of covering it in depth in this series. 23 | 24 | So, if you want to learn Concurrency in a very fun and interesting way, ho along :) 25 | 26 | 27 | ``` 28 | gcc -Wall -lpthread 29 | ./a.out 30 | ``` --------------------------------------------------------------------------------