Deadlocks, Livelocks, and Starvation

Deadlocks

= program in which all concurrent processes are waiting on one another.

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	ch1 := make(chan int)
	ch2 := make(chan int)

	wg.Add(1)
	go func() {
		defer wg.Done()
		// Attempting to send data to ch2, but no one is receiving.
		ch2 <- 42
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		// Attempting to send data to ch1, but no one is receiving.
		ch1 <- 23
	}()

	wg.Wait()
}

Livelocks

= programs that are actively performing concurrent operations, but these operations do nothing to move the state of the program forward.

/* Two goroutines are trying to access a shared resource (condition) protected by a mutex (mu). 
They repeatedly check the condition and take actions to resolve the conflict. However, their actions lead to an ongoing conflict without making progress, resulting in a livelock.*/

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex
	condition := true

	go func() {
		for {
			mu.Lock()
			if condition {
				mu.Unlock()
				continue
			}
			fmt.Println("Goroutine 1")
			condition = true
			mu.Unlock()
		}
	}()

	go func() {
		for {
			mu.Lock()
			if !condition {
				mu.Unlock()
				continue
			}
			fmt.Println("Goroutine 2")
			condition = false
			mu.Unlock()
		}
	}()

	select {}
}

Starvation

= any situation where a concurrent process cannot get all the resources it needs to perform work.

/* One goroutine continually acquires and releases a lock, preventing the starving goroutine from ever acquiring it. */

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex

	// Starving goroutine
	go func() {
		mu.Lock()
		fmt.Println("Starving goroutine: acquired the lock")
		mu.Unlock()
	}()

	// Goroutine that keeps the lock
	go func() {
		for {
			mu.Lock()
			fmt.Println("Lock keeper goroutine: acquired the lock")
			mu.Unlock()
		}
	}()

	select {}
}

Last updated