Golang Channel

Golang Channel

Send & Receive Golang Channel

package main

import "fmt"

func main() {
	c := make(chan int)

	go foo(c)

	bar(c)

	fmt.Printf("Exiting...")
}

//send
func foo(c chan<- int) {
	c <- 42
}

//receive
func bar(c <-chan int) {
	fmt.Println(<-c)
}

Buffered Channels

  • In Go, a channel with a buffer size >= 1 is known as an buffered channel.
  • ie. ch := make(chan int, 3) // Buffered channel with a capacity of 3

Use Cases

  1. Decoupling Producer and Consumer:
    • Buffered channels allow a producer to generate data independently of the consumer. The producer can continue generating data up to the buffer’s capacity without waiting for the consumer to be ready.
  2. Concurrency Control:
    • Buffered channels can be used to limit the number of concurrently running goroutines or to control the rate at which data is processed.
  3. Performance Optimization:
    • When performance is critical and the overhead of context switching (from blocking in unbuffered channels) needs to be minimized, buffered channels can help by allowing some level of asynchronous processing.

Advantages

  • Improved Throughput: By decoupling the sender and receiver, buffered channels can improve the throughput of your system.
  • Flexibility: Buffered channels provide more flexibility in designing systems where producers and consumers operate at different rates.

Example

package main

import (
	"fmt"
)

func main() {
	ch := make(chan int, 3) // Buffered channel with a capacity of 3

	ch <- 1
	ch <- 2
	ch <- 3

	fmt.Println(<-ch) // Outputs: 1
	fmt.Println(<-ch) // Outputs: 2
	fmt.Println(<-ch) // Outputs: 3
}

Unbuffered Channel

  • In Go, a channel with a buffer size of 0 is known as an unbuffered channel.
  • The primary characteristic of an unbuffered channel is that it requires both a sender and a receiver to be ready at the same time in order for communication to occur.
  • If either the sender or the receiver is not ready, the operation will block.

Use Cases

  1. Synchronization:
    • Goroutine Coordination: Unbuffered channels are perfect for cases where you need to ensure that two goroutines are synchronized. For example, one goroutine may need to wait until another goroutine has completed its task before proceeding.
    • Handshaking: If you need strict control over when data is passed between goroutines, unbuffered channels enforce that both the sender and receiver are ready at the same time.
  2. Pipeline Stages:
    • When setting up stages in a pipeline where each stage processes data and then passes it to the next, unbuffered channels can ensure that data is processed as it arrives, without being buffered.
  3. Resource Management:
    • If a resource should only be accessed by one goroutine at a time and you want to serialize access to that resource, an unbuffered channel can help enforce that one goroutine sends and another receives, thus managing access to the resource.

Advantages

  • Strict Synchronization: Ensures that data is passed only when both sender and receiver are ready.
  • Simplicity: Simple and easy to reason about when coordinating goroutines.

Example

package main

import (
	"fmt"
	"time"
)

func worker(done chan bool) {
	fmt.Println("Working...")
	time.Sleep(time.Second)
	fmt.Println("Done!")
	done <- true // Send signal to indicate completion
}

func main() {
	done := make(chan bool)
	go worker(done)
	<-done // Wait for the worker to finish
}

When to Use Each

  • Buffered Channels: Use when you want to decouple the timing between producers and consumers, allow for some level of buffering in your communication, or improve the performance by reducing blocking. They are suitable for tasks where the producer may generate data faster than the consumer can process it, or where the communication latency needs to be minimized.
  • Unbuffered Channels: Use when you need precise synchronization between goroutines or when the order of operations between goroutines is important. They are ideal for tasks where you need to wait for another goroutine to finish before proceeding.

Real-World Scenarios

  • Buffered Channels: Frequently used in scenarios like job queues, message passing between different parts of a system where rate control is needed, and situations where you want to avoid blocking the producer.
  • Unbuffered Channels: Often used in situations like task completion signals, coordinating worker pools, and pipeline stages where strict order and timing control are needed.

Conclusion

Both unbuffered and buffered channels have their place in real-world Go programming. The choice depends on whether you need tight synchronization or more flexibility and decoupling in your concurrent operations. Use unbuffered channels for strict handshakes and synchronization, and buffered channels when you need to improve throughput and decouple producers and consumers.

Various Cases

Success -1

Single item processed using channel

package main

import "fmt"

func main() {

	ch := make(chan int, 1)
	// pushing input 15 in channel 'ch'
	ch <- 15

  // processing items from 'ch'
	val, ok := <-ch
	fmt.Println(val, ok)
	fmt.Printf("main exiting\n")
}

******************************
Output
******************************

go run channel.go
15 true
main exiting

Error-1

all goroutines are asleep – deadlock!

package main

func main() {

  // no channel size provided
 	ch := make(chan int)
 	
  // The send operation (ch <- 15) will block indefinitely,
	// because there is no corresponding receive operation
	// in a separate goroutine to retrieve the value from the channel.
	// This will result in a deadlock and eventually cause a runtime panic
	ch <- 15

	// close(ch)
	// ch := make(chan int, 1)
	// val, ok := <-ch
	// fmt.Println(val, ok)
	fmt.Printf("main exiting\n")
}

******************************
Output
******************************
go run channel.go
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
channel.go:6 +0x34
exit status 2

Solution-1.1

  • Make both producer & consumer ready for data processing
package main

import "fmt"

func main() {

	// channel of size 0
	ch := make(chan int)
	fmt.Printf("channel size=%d\n", len(ch))

	// making consumer ready
	go func() {
		val, ok := <-ch
		fmt.Println(val, ok)
	}()

	ch <- 15

	fmt.Println("main exiting")
}

******************************
Output
******************************
channel size=0
15 true
main exiting

Solution-1.2

  • Provide size for channel ‘ch’ e.g. 1
package main

func main() {

	ch := make(chan int, 1)
	ch <- 1
}

******************************
Output
******************************
go run channel.go
<no error>

Channel Range

package main

import "fmt"

func main() {
	c := make(chan int)

	go foo(c)

	for v := range c {
		fmt.Println(v)
	}

	fmt.Printf("Exiting...")
}

// send
func foo(c chan<- int) {
	for i := 0; i < 10; i++ {
		c <- i
	}
	// close is very important 
	// after  sending all values in channel from producer
	close(c)
}

Channel Select

In the given code, closing channels is not strictly required because of how the program’s logic is structured. Here’s a detailed explanation of why this works:


Why Close is Typically Used in Go

  1. Signaling Completion:
    • Closing a channel signals that no more values will be sent on it.
    • Receivers can detect this using the v, ok := <-channel pattern, where ok is false if the channel is closed.
  2. Avoiding Deadlocks:
    • Without closing a channel, goroutines waiting to read from it might block indefinitely if no more values are sent.
  3. Iterating Over Channels:
    • To range over a channel (for v := range ch), it must be closed to terminate the loop gracefully.

Why It Works Without Closing in This Code

  1. Quit Channel Handles Termination:
    • The quit channel is used explicitly to signal when the program should stop receiving values.When a value is sent to quit, the receive function detects this in the select block, prints the value, and exits using return
case v := <-quit:
    fmt.Printf("returning from loop = %d\n", v)
    return
    • This mechanism ensures that the program terminates the receive loop without relying on closed channels.
  1. No Infinite Blocking:
    • The send function sends exactly 10 values (5 odd and 5 even) and then signals termination with a single value sent to quit.
    • Since quit is sent and immediately received, there are no goroutines left blocked indefinitely. This avoids deadlocks.
  2. Channels Are Not Iterated:
    • The code does not use for v := range ch to iterate over channels, which would require the channels to be closed.
    • Instead, values are read explicitly with <-odd, <-even, and <-quit, which do not require the channels to be closed.
  3. Garbage Collection:
    • Since the program terminates naturally after completing the receive loop, the channels are cleaned up by Go’s garbage collector.

When Would Closing Be Required?

  1. When Using range:
    • If you use for v := range ch, the channel must be closed to end the loop.
  2. When Signaling Across Multiple Goroutines:
    • If many goroutines are reading from a channel, closing the channel is a clear and efficient way to signal all of them that no more data is coming.
  3. Preventing Resource Leaks in Long-Running Programs:
    • In long-running applications, leaving channels open could cause memory leaks if they are not garbage-collected.

Conclusion

In this program, closing the channels (odd, even, and quit) is unnecessary because:

  • The quit channel explicitly signals when to stop receiving.
  • There are no infinite loops or unbounded data streams that would lead to deadlocks or resource leaks.
  • The program exits naturally after processing all the data.

However, best practices suggest closing channels when their usage has ended, especially in complex or long-running programs, to improve clarity and robustness.

package main

import "fmt"

func main() {
	odd := make(chan int)
	even := make(chan int)
	quit := make(chan int)

	go send(odd, even, quit)

	receive(odd, even, quit)

	fmt.Printf("Exiting...")
}

func send(odd, even, quit chan int) {

	for i := 1; i <= 10; i++ {
		if i%2 == 0 {
			even <- i
		} else {
			odd <- i
		}
	}
	// both works fine
	quit <- 1
	// quit <- 0

}

// send
func receive(odd, even, quit chan int) {
	for {
		select {
		case v := <-odd:
			fmt.Printf("odd = %d\n", v)

		case v := <-even:
			fmt.Printf("even = %d\n", v)

		case v := <-quit:
			fmt.Printf("returning from loop = %d\n", v)
			return
		}
	}
}

Output

odd = 1
even = 2
odd = 3
even = 4
odd = 5
even = 6
odd = 7
even = 8
odd = 9
even = 10
returning from loop = 1
Exiting...
Program exited.

Fan In

package main

import (
	"fmt"
	"sync"
)

func main() {
	even := make(chan int)
	odd := make(chan int)
	fanin := make(chan int)

	go send(even, odd)

	go receive(even, odd, fanin)

	for v := range fanin {
		fmt.Printf("fanin=%d\n", v)
	}

	fmt.Println("about to exit")
}

// send channel
func send(even, odd chan<- int) {
	for i := 0; i < 10; i++ {
		if i%2 == 0 {
			even <- i
		} else {
			odd <- i
		}
	}
	close(even)
	close(odd)
}

// receive channel
func receive(even, odd <-chan int, fanin chan<- int) {
	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		for v := range even {
			fanin <- v
		}
		wg.Done()
	}()

	go func() {
		for v := range odd {
			fanin <- v
		}
		wg.Done()
	}()

	wg.Wait()
	close(fanin)
}

Output

fanin=0
fanin=2
fanin=1
fanin=3
fanin=5
fanin=4
fanin=6
fanin=8
fanin=7
fanin=9
about to exit

Program exited.

Fan Out

package main

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

func main() {
	c1 := make(chan int)
	c2 := make(chan int)

	go populate(c1)

	go fanOutIn(c1, c2)

	for v := range c2 {
		fmt.Println(v)
	}

	fmt.Println("about to exit")
}

func populate(c chan int) {
	for i := 0; i < 10; i++ {
		c <- i
	}
	close(c)
}

func fanOutIn(c1, c2 chan int) {
	var wg sync.WaitGroup
	for v := range c1 {
		wg.Add(1)
		go func(v2 int) {
			c2 <- timeConsumingWork(v2)
			wg.Done()
		}(v)
	}
	wg.Wait()
	close(c2)
}

func timeConsumingWork(n int) int {
	time.Sleep(time.Microsecond * time.Duration(rand.Intn(500)))
	return n + rand.Intn(1000)
}

Output

185
498
382
906
469
71
168
108
333
30
about to exit

Program exited.

Please visit https: https://codeandalgo.com for more such contents.

Leave a Reply

Your email address will not be published. Required fields are marked *