Concurrency: C, Go and Rust
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 |
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.- Each value in Rust has a variable that’s called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
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