Skip to content

Instantly share code, notes, and snippets.

@1UC1F3R616
Last active June 25, 2024 16:06
Show Gist options
  • Save 1UC1F3R616/9866b6e897b26b9401a59b1f32c2ea21 to your computer and use it in GitHub Desktop.
Save 1UC1F3R616/9866b6e897b26b9401a59b1f32c2ea21 to your computer and use it in GitHub Desktop.
Go Routines and Channels Done Right.

Using Select to get output from multiple channels based on when they are ready:

package main

import (
	"fmt"
	"time"
)

func main() {
	channel1 := make(chan string)
	channel2 := make(chan string)

	go func() {
		for i := 0; i < 5; i++ {
			channel1 <- "I'll print every 100ms"
			time.Sleep(time.Millisecond * 100)
			
		}
	}()

	go func() {
		for i := 0; i < 5; i++ {
			channel2 <- "I'll print every 1s"
			time.Sleep(time.Second * 1)
			
		}
	}()

	for i := 0; i < 5; i++ {
		select {
		case message1 := <-channel1:
			fmt.Println(message1)
		case message2 := <-channel2:
			fmt.Println(message2)
		}
	}
}

Using default along with above example to do something when no channel has anything to send

package main

import (
	"fmt"
	"time"
)

func main() {
	channel1 := make(chan string)
	channel2 := make(chan string)

	go func() {
		for i := 0; i < 5; i++ {
			channel1 <- "I'll print every 100ms"
			time.Sleep(time.Millisecond * 100)
			
		}
	}()

	go func() {
		for i := 0; i < 5; i++ {
			channel2 <- "I'll print every 1s"
			time.Sleep(time.Second * 1)
			
		}
	}()

	for i := 0; i < 5; i++ {
		select {
		case message1 := <-channel1:
			fmt.Println(message1)
		case message2 := <-channel2:
			fmt.Println(message2)
		default:
			fmt.Println("No channel is ready")
		}
	}
}

Using Buffered Channel in Golang: No Deadlock happens in this example because it won't get blocked till capacity is hit

package main
import "fmt"

func main() {
	mychannel := make(chan int, 2)
	mychannel <- 10
	fmt.Println(<-mychannel)
}

WaitGroup

  • A WaitGroup blocks a program and waits for a set of goroutines to finish before moving to the next steps of execution.
We can use WaitGroups through the following functions:

.Add(int): This function takes in an integer value which is essentially the number of goroutines which the waitgroup has to wait for. This function must be called before we execute a goroutine.
.Done(): This function is called within the goroutine to signal that the goroutine has successfully executed.
.Wait(): This function blocks the program until all the goroutines specified by Add() have invoked Done() from within.
package main
import ( 
  "fmt"
  "sync"
)

func WelcomeMessage(wg *sync.WaitGroup){
    fmt.Println("Welcome to Educative!")
    wg.Done()
}

func main() {
  var wg sync.WaitGroup

  wg.Add(2)

  go WelcomeMessage(&wg)
  go func(){  
    fmt.Println("Hello World!")
    wg.Done()
  }()

  wg.Wait()

}

Mutex

  • To solve race conditions.
  • A mutex, or a mutual exclusion prevents other processes from entering a critical section of data while a process occupies it.
  • In general, the section of code which is accessing or updating the shared resources is called the critical section. Mutexes provide us with a locking mechanism which enables only one process to access the critical section at a time.
sync.mutex has two methods:

.Lock() : acquires/holds the lock
.Unlock(): releases the lock
package main
import ( "fmt"
        "sync")

func deposit(balance *int,amount int, myMutex *sync.Mutex, myWaitGroup *sync.WaitGroup){
    myMutex.Lock()
    *balance += amount //add amount to balance
    myMutex.Unlock()
    myWaitGroup.Done()
}

func withdraw(balance *int, amount int, myMutex *sync.Mutex, myWaitGroup *sync.WaitGroup){
    myMutex.Lock()
    *balance -= amount //subtract amount from balance
    myMutex.Unlock()
    myWaitGroup.Done()
}

func main() {
  
    balance := 100 
    var myWaitGroup sync.WaitGroup
    var myMutex sync.Mutex

    myWaitGroup.Add(2)
    go deposit(&balance,10, &myMutex, &myWaitGroup) //depositing 10
    withdraw(&balance, 50,&myMutex, &myWaitGroup) //withdrawing 50

    myWaitGroup.Wait()
    fmt.Println(balance) 


}
  • RWMutex stands for Reader/Writer mutual exclusion and is essentially the same as Mutex, but it gives the lock to more than one reading process or just a writing process.
  • RWMutex provides us with more control over memory.

Generator Pattern

  • pattern of concurrency, which is about generators that return channels as returning argument.
  • Generators return the next value in a sequence each time they are called. This means that each value is available as an output before the generator computes the next value. Hence, this pattern is used to introduce parallelism in our program.
package main

import "fmt"

func updatePosition(name string) <-chan string { 
	positionChannel := make(chan string)

	go func() {
		for i := 0; ; i++ {
			positionChannel <- fmt.Sprintf("%s %d", name , i)
		}
	}()

	return positionChannel
}

func main() {
	positionChannel1 := updatePosition("Legolas :") 
	positionChannel2 := updatePosition("Gandalf :")
	

	for i := 0; i < 5; i++ {
		fmt.Println(<-positionChannel1)
		fmt.Println(<-positionChannel2)
	}

	fmt.Println("Done with getting updates on positions.")
}

Output:

Legolas : 1
Gandalf : 1
Legolas : 2
Gandalf : 2
Legolas : 3
Gandalf : 3
Legolas : 4
Gandalf : 4
Done with getting updates on positions.

Fan-In, Fan-Out

  • Fan-In, Fan-Out techniques which are used to multiplex and demultiplex data in Go. Fan-In, Fan-Out

  • These two operations are blocking each other

    fmt.Println(<-positionChannel1)
    fmt.Println(<-positionChannel2)
  • They are taking turns not only in printing value on to the console but also in proceeding to the next computation.
package main

import ( "fmt"
		  "math/rand"
		  "time")

func updatePosition(name string) <-chan string { 
	positionChannel := make(chan string)

	go func() {
		for i := 0; ; i++ {
			positionChannel <- fmt.Sprintf("%s %d", name , i)
			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
		}
	}()

	return positionChannel
}

func fanIn(mychannel1, mychannel2 <-chan string) <-chan string {
	mychannel := make(chan string) 

	go func() { 
		for {
			mychannel <- <-mychannel1 
		}
	}()

	go func() { 
		for {
			mychannel <- <-mychannel2
		}
	}()

	return mychannel
}


func main() {
	positionsChannel := fanIn(updatePosition("Legolas :"), updatePosition("Gandalf :"))
	

	for i := 0; i < 10; i++ {
		fmt.Println(<-positionsChannel)
	}

	fmt.Println("Done with getting updates on positions.")
}
  • Output is random now.
  • Here we take in two channels as input and specify one channel as the return argument, i.e., mychannel which is the Fan-In channel.
  • The only difference comes from the fact that mychannel1 and mychannel2 are communicating with mychannel now instead of directly communicating with the main routine as before.

Sequencing

  • A pattern used for sequencing in a program by sending channel over a channel.
  • If we don’t want to block the code and introduce sequence to our program instead of randomness
package main

import ( "fmt"
		  "math/rand"
		  "time")


type CookInfo struct {
	foodCooked  string
	waitForPartner chan bool 
}



func cookFood(name string) <-chan CookInfo { 
	
	cookChannel := make(chan CookInfo)
	wait := make(chan bool)
	go func() {
		for i := 0; ; i++ {
			cookChannel<- CookInfo{fmt.Sprintf("%s %s", name,"Done") , wait}
			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)

			<-wait
		}
	}()

	return cookChannel
}

func fanIn(mychannel1, mychannel2 <-chan CookInfo) <-chan CookInfo {
	mychannel := make(chan CookInfo)

	go func() { 
		for {
			mychannel <- <-mychannel1 
		}
	}()

	go func() { 
		for {
			mychannel <- <-mychannel2
		}
	}()

	return mychannel
}


func main() {
	gameChannel := fanIn(cookFood("Player 1 : "), cookFood("Player 2 :"))
	

	for round := 0; round < 3; round++ {
		food1 := <-gameChannel
		fmt.Println(food1.foodCooked)

		food2 := <-gameChannel
		fmt.Println(food2.foodCooked)

		food1.waitForPartner <- true
		food2.waitForPartner <- true

		fmt.Printf("Done with round %d\n", round+1)
	}

	fmt.Println("Done with the competition")
}
@1UC1F3R616
Copy link
Author

Timeout using Select Statement

  • A pattern which uses the time.After function in a select statement.
  • time.after returns a channel that blocks the code for the specified duration. After that duration, the channel delivers the current time but only once.
package main
import ( "fmt"
         "time"
         "math/rand")


func main() {
  dynamite := make(chan string)

  go func(){
    rand.Seed(time.Now().UnixNano())
    time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
    dynamite <- "Dynamite Diffused!"
  }()
    
  for{
    select
    { case s := <-dynamite:
        fmt.Println(s)
        return
      case <-time.After(time.Duration(rand.Intn(500)) * time.Millisecond):
        fmt.Println("Dynamite Explodes!")  
        return      
    }
  }
}

@1UC1F3R616
Copy link
Author

Google Talks about Advanced Concurrency in Go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment