Golang Tutorial

Fundamentals

Control Statements

Functions & Methods

Structure

Arrays & Slices

String

Pointers

Interfaces

Concurrency

Unidirectional Channel in Golang

In Go, channels are used primarily for communication between goroutines, providing a way to send and receive data with synchronization. By default, channels are bidirectional, meaning you can both send and receive values on them. However, sometimes you might want to be explicit about the direction of data flow by using unidirectional channels.

Unidirectional channels are either send-only or receive-only. This tutorial covers the basics of unidirectional channels in Go.

1. Declaring Unidirectional Channels

  • Send-only channel: You can only send data to the channel.

    var chSend chan<- int
    
  • Receive-only channel: You can only receive data from the channel.

    var chReceive <-chan int
    

2. Using Unidirectional Channels

package main

import (
	"fmt"
	"time"
)

func sendData(chSend chan<- int) {
	for i := 0; i < 5; i++ {
		chSend <- i
		time.Sleep(time.Second)
	}
	close(chSend)
}

func receiveData(chReceive <-chan int) {
	for v := range chReceive {
		fmt.Println(v)
	}
}

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

	go sendData(ch)
	receiveData(ch)
}

In the example above:

  • The sendData function takes a send-only channel as an argument. It can only send data to this channel.
  • The receiveData function takes a receive-only channel. It can only read data from this channel.
  • The main function creates a bidirectional channel but when calling the other functions, it implicitly converts to the correct direction.

3. Limitations

  1. You can't change the direction of a channel once it's set: If you have a receive-only channel, you can't later make it bidirectional or send-only, and vice-versa.

  2. Unidirectional channels are mainly for function and method parameters: It's common to use unidirectional channels as function/method parameters to restrict usage inside the function/method. This makes code more readable and provides a level of safety.

4. Use Cases

  1. Safety: By making a channel send-only or receive-only, you're indicating its intended use. This can prevent errors, like accidentally reading from a channel that should only be written to.

  2. Code clarity: Using unidirectional channels can make code easier to understand by clarifying the intent of the channel.

Key Takeaways:

  • Unidirectional channels can either be send-only or receive-only.
  • They are useful for ensuring that a channel is only used for a specific purpose (either sending or receiving).
  • It's common to use them as function or method parameters to enhance code clarity and safety.

Remember, while unidirectional channels can be a powerful feature for writing clear and safe concurrent code, they are not always necessary. Often, bidirectional channels are sufficient, especially when the intended usage is clear.

  1. Golang unidirectional channel example:

    Unidirectional channels restrict the direction of data flow, either for sending or receiving.

    package main
    
    import "fmt"
    
    func main() {
        // Creating unidirectional channels
        sendOnlyChan := make(chan<- int)
        receiveOnlyChan := make(<-chan int)
    
        fmt.Printf("Send-only channel: %T\n", sendOnlyChan)
        fmt.Printf("Receive-only channel: %T\n", receiveOnlyChan)
    }
    
  2. Send-only channel in Golang:

    Creating a send-only channel allows only sending data into the channel.

    package main
    
    import "fmt"
    
    func sendData(ch chan<- int, data int) {
        ch <- data
    }
    
    func main() {
        sendOnlyChan := make(chan<- int)
        go sendData(sendOnlyChan, 42)
    }
    
  3. Receive-only channel in Golang:

    Creating a receive-only channel allows only receiving data from the channel.

    package main
    
    import "fmt"
    
    func receiveData(ch <-chan int) {
        data := <-ch
        fmt.Println("Received data:", data)
    }
    
    func main() {
        receiveOnlyChan := make(<-chan int)
        go receiveData(receiveOnlyChan)
    }
    
  4. Unidirectional channel vs bidirectional channel in Go:

    Unidirectional channels restrict the direction of data flow, whereas bidirectional channels allow both sending and receiving.

    package main
    
    import "fmt"
    
    func main() {
        // Unidirectional channel
        sendOnlyChan := make(chan<- int)
        receiveOnlyChan := make(<-chan int)
    
        // Bidirectional channel
        bidirectionalChan := make(chan int)
    
        fmt.Printf("Send-only channel: %T\n", sendOnlyChan)
        fmt.Printf("Receive-only channel: %T\n", receiveOnlyChan)
        fmt.Printf("Bidirectional channel: %T\n", bidirectionalChan)
    }
    
  5. Golang select statement with unidirectional channels:

    The select statement can be used with unidirectional channels for non-blocking communication.

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        sendChan := make(chan<- int)
        receiveChan := make(<-chan int)
    
        select {
        case sendChan <- 42:
            fmt.Println("Sent data to sendChan")
        case data := <-receiveChan:
            fmt.Println("Received data from receiveChan:", data)
        default:
            fmt.Println("No communication")
        }
    }
    
  6. Closing a unidirectional channel in Go:

    Unidirectional channels can be closed, and attempting to send or receive on a closed channel will cause a panic.

    package main
    
    import "fmt"
    
    func main() {
        sendOnlyChan := make(chan<- int)
        close(sendOnlyChan) // Closing a send-only channel will cause a panic
    
        receiveOnlyChan := make(<-chan int)
        close(receiveOnlyChan) // Closing a receive-only channel will cause a panic
    }
    
  7. Buffered unidirectional channels in Golang:

    Unidirectional channels can also be buffered to allow a certain number of elements to be stored in the channel without a corresponding receiver.

    package main
    
    import "fmt"
    
    func main() {
        sendOnlyChan := make(chan<- int, 2)
        sendOnlyChan <- 42
        sendOnlyChan <- 33
        // sendOnlyChan <- 55 // Uncommenting this line would result in a deadlock
    }
    
  8. Using unidirectional channels for goroutine communication:

    Unidirectional channels are often used for communication between goroutines.

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func producer(ch chan<- int, wg *sync.WaitGroup) {
        defer wg.Done()
        ch <- 42
    }
    
    func consumer(ch <-chan int, wg *sync.WaitGroup) {
        defer wg.Done()
        data := <-ch
        fmt.Println("Received data:", data)
    }
    
    func main() {
        ch := make(chan int)
        var wg sync.WaitGroup
    
        wg.Add(1)
        go producer(ch, &wg)
    
        wg.Add(1)
        go consumer(ch, &wg)
    
        wg.Wait()
        close(ch)
    }
    
  9. Golang fan-out, fan-in with unidirectional channels:

    Fan-out involves multiple goroutines reading from a single channel, and fan-in involves multiple goroutines writing to a single channel.

    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func producer(ch chan<- int, wg *sync.WaitGroup) {
        defer wg.Done()
        for i := 0; i < 5; i++ {
            ch <- i
        }
    }
    
    func consumer(ch <-chan int, resultChan chan<- int, wg *sync.WaitGroup) {
        defer wg.Done()
        for {
            data, ok := <-ch
            if !ok {
                break
            }
            resultChan <- data * 2
        }
    }
    
    func main() {
        ch := make(chan int)
        resultChan := make(chan int)
        var wg sync.WaitGroup
    
        wg.Add(1)
        go producer(ch, &wg)
    
        for i := 0; i < 3; i++ {
            wg.Add(1)
            go consumer(ch, resultChan, &wg)
        }
    
        go func() {
            wg.Wait()
            close(resultChan)
        }()
    
        for result := range resultChan {
            fmt.Println("Result:", result)
        }
    }