Skip to content

Channels

Channels in Go are a powerful feature that allows goroutines to communicate with each other and synchronize their execution. They are used to pass data between goroutines and can be thought of as pipes that connect concurrent goroutines.

You can create a channel using the make function. Channels are typed by the values they convey. You can send data into a channel using the channel <- syntax and receive data from a channel using the <-channel syntax.

Example

run command
go run src/multithereading/basic_channel.go
package main

import "fmt"

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

    go func() {
        channel <- "Hello Word!"
    }()

    msg := <-channel
    fmt.Println(msg)
}
output
Hello Word!

Channel Directions

In Go, channels can be created with a specific direction, which can be either send-only, receive-only, or bidirectional. This feature allows you to enforce certain restrictions on how a channel can be used, which can help prevent errors and make your code easier to understand.

Send-Only Channels

Syntax: chan<-.

ch := make(chan<- int) // Send-only channel
ch <- 42 // Sending data

Receive-Only Channels

Syntax: <-chan

ch := make(<-chan int) // Receive-only channel
data := <-ch // Receiving data
Full Example

run command
go run src/multithereading/channel_directions.go
package main

import "fmt"

func setData(ch chan<- string) {
    super_secret := "Corinthians"
    ch <- super_secret
}

func readData(ch <-chan string) {
    data := <-ch
    fmt.Println(data)
}

func main() {
    ch := make(chan string)
    go setData(ch)

    readData(ch)
}
output
Corinthians

The Bidirectional channel is denoted by the chan syntax, without any direction indicator.

Buffered Channels

By default, sends and receives block until both the sender and receiver are ready. However, you can create a buffered channel that can hold a certain number of values before blocking. For example, to create a buffered channel that can hold up to 2 strings. In this case, the send operation won't block until the channel's buffer is full, and the receive operation won't block until the channel's buffer is empty.

Example

run command
go run src/multithereading/buffered_channel.go
package main

import "fmt"

func main() {
    channel := make(chan string, 2)

    channel <- "message 1"
    channel <- "message 2"

    msg1 := <-channel
    msg2 := <-channel
    fmt.Println(msg1)
    fmt.Println(msg2)
}
output
message 1
message 2

Deadlock

If a goroutine is sending data on a channel, then it is expected that some other goroutine should be receiving the data. If this does not happen, then the program will panic at runtime with a deadlock error. Similarly, if a goroutine is waiting to receive data from a channel, then some other goroutine is expected to write data on that channel, else the program will panic.

Range and Close

You can use the range keyword to read values from a channel until it's closed. When the channel is closed and drained, the range loop will terminate.

Example

run command
go run src/multithereading/range_channel.go
package main

import "fmt"

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

    go func() {
        messages <- "ping 1"
        messages <- "ping 2"
        close(messages)
    }()

    for msg := range messages {
        fmt.Println(msg)
    }
}
output
ping 1
ping 2

Range with WaitGroup

run command
go run src/multithereading/range_waitgroup.go
package main

import (
    "fmt"
    "sync"
)

func publish(ch chan int, wg *sync.WaitGroup) {
    for i := 0; i < 10; i++ {
        fmt.Printf("Publishing value :%d\n", i)
        wg.Add(1) // using wg.Add(10) on line 27, this need to be removed
        ch <- i
    }
    close(ch)
}

func subscribe(ch chan int, wg *sync.WaitGroup) {
    for x := range ch {
        fmt.Printf("Subscribe value: :%d\n", x)
        wg.Done()
    }
}

func main() {
    ch := make(chan int)
    wg := sync.WaitGroup{}
    // wg.Add(10)
    go publish(ch, &wg)
    subscribe(ch, &wg) // or go subscribe(ch, &wg) using wg.Add(10) on line 27
    wg.Wait()
}
output
Publishing value :0
Publishing value :1
Subscribe value: :0
Subscribe value: :1
Publishing value :2
Publishing value :3
Subscribe value: :2
Subscribe value: :3
Publishing value :4
Publishing value :5
Subscribe value: :4
Subscribe value: :5
Publishing value :6
Publishing value :7
Subscribe value: :6
Subscribe value: :7
Publishing value :8
Publishing value :9
Subscribe value: :8
Subscribe value: :9

Examples

  1. Api Race Challenge
Fake Load Balancer

run command
go run src/multithereading/fake_loadbalancer.go
package main

import (
    "fmt"
    "time"
)

func worker(workerId int, data <-chan int) {
    for x := range data {
        fmt.Printf("Worked %d received %d\n", workerId, x)
        time.Sleep(time.Second)
    }
}

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

    workersNum := 1000

    for i := 0; i < workersNum; i++ {
        go worker(i, ch)
    }

    for i := 0; i < 2000; i++ {
        ch <- i
    }
}
output
...
Worked 9 received 1926
Worked 903 received 1997
Worked 975 received 1971
Worked 765 received 1942
Worked 274 received 1839
Worked 763 received 1856
Worked 914 received 1860
Worked 915 received 1864
Worked 702 received 1877
Worked 981 received 1898
Worked 797 received 1879
Worked 774 received 1853
Worked 798 received 1882
Worked 320 received 1275
Worked 434 received 1276
Worked 108 received 1073
Worked 530 received 1277

References

  1. Channels
  2. How to use Go channels
  3. Golang: Channel Directions