Concurrency: C, Go and Rust


Responsive image
Single-Threaded and Multi-Threaded Address Spaces

Lets jump into the problem in the table written in C1 and Golang. Guess what is the output you are expecting for variable counter at the end of execution of C and Go2 programs in the table?

C Go
        #include <stdio.h>
        #include <pthread.h>

        static volatile int counter = 0;

        void *
        mythread(void *arg)
        {
          int i;

          printf("%s: begin\n", (char *) arg);
          for (i = 0; i < 1e7; i++) {
              counter = counter + 1;
          }

          printf("%s: done\n", (char *) arg);
          return NULL;
        }

        int
        main(int argc, char *argv[])
        {
          pthread_t p1, p2;
          printf("main: begin (counter = %d)\n", counter);
          pthread_create(&p1, NULL, mythread, "Thread1");
          pthread_create(&p2, NULL, mythread, "Thread2");

          // join waits for the threads to finish
          pthread_join(p1, NULL);
          pthread_join(p2, NULL);
          printf("main: done with both (counter = %d)\n", counter);
          return 0;
        }
        package main

        import "fmt"

        var (
          counter int
        )

        func mythread(arg string, done chan struct{}) {
          var i int

          fmt.Println("Thread name: ", arg)
          for i = 0; i < 1e7; i++ {
              counter++
          }
          done <- struct{}{}
          fmt.Println("Finished Thread name: ", arg)
        }

        func main() {
          var done = make(chan struct{})

          go mythread("thread1", done)
          go mythread("thread2", done)

          <- done
          <- done

          fmt.Println("counter is ", counter)
        }
        
Try it in C online editor Try it in Go online editor

Data race

Uncontrolled access of shared data between threads leads to data race. Data race bugs are hard to diagnose and fix. But where is the data race in the above programs written in C and Go in the table above? So, having a controlled access to the critical section to the code ensures that data race doesn't happen. Mutual exclusion using locks are provided by different languages. But onus is on programmer who has to be aware of the data races existing in the code. In large code bases, data races often sneaks into the program even when written by seasoned programmers.

Lets try same experiment in Rust programming language, a relatively new one introduced by Mozilla.

Rust
            use std::thread;

            static mut COUNTER: i32 = 0;
            
            fn mythread(arg: &str) {
                println!("Begin {}!", arg);
                for _i in 1..1000000 {
                    COUNTER = COUNTER + 1;
                }
                println!("Finished {}!", arg);
            }
            
            fn main() {
                let handle_1 = thread::spawn(|| {
                    mythread("thread1");
                });
            
                let handle_2 = thread::spawn(|| {
                    mythread("thread2");
                });
            
                handle_1.join().unwrap();
                handle_2.join().unwrap();
                println!("Value is {}", COUNTER);
            }
          
Try it in Rust online editor
What happened to the compilation of the rust program?

Rust and it's ownership model

Rust is emphasising deeply on memory safety, performance and prevention of data races in concurrent programs at compilation time itself. Rust's feature to provide memory safety is it's unique ownership rules and it is as follows.
  1. Each value in Rust has a variable that’s called its owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value will be dropped.
With the ownership rules defined by Rust, look for shared variable COUNTER's ownership at the time of programs execution in the program given. Do you think, global variable COUNTER abides to the ownership rules of Rust?


1 These examples are inspired from wonderful book Operating Systems: Three Easy Pieces by Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau. 2 Source code for this blog

2 Source code for this blog