Skip to content

Commit

Permalink
Add channels concept
Browse files Browse the repository at this point in the history
  • Loading branch information
RezaSi committed Jul 22, 2022
1 parent 5dba26f commit 891e190
Show file tree
Hide file tree
Showing 15 changed files with 682 additions and 0 deletions.
5 changes: 5 additions & 0 deletions concepts/channels/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"blurb": "Channels are the pipes that connect concurrent goroutines.",
"authors": ["RezaSi"],
"contributors": []
}
138 changes: 138 additions & 0 deletions concepts/channels/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# About

[Goroutines](https://go.dev/tour/concurrency/1) are a way to execute concurrent code in Go.
Goroutines are works independently, if they need to communicate, Go preferred to use channels.
Create a new channel with `make(chan val-type)`.
Channels are typed by the values they convey.

```go
ch := make(chan int) // create new channel
ch <- value // Send value(same as channel type) to channel ch.
value := <-ch // Receive from ch and assign value to value variable.
```

By default, sends and receives block wait until the other side is ready.
This allows goroutines to synchronize without explicit locks or condition variables.

Example of channel usage, if we want to findout the value exists in the slice, we can split the slice into two parts, find separately in other goroutines, and then send the result to the main goroutine.

```go
package main

import "fmt"

func exist(slice []int, x int, resultChan chan bool) {
found := false
for _, v := range slice {
if v == x {
found = true
break
}
}
resultChan <- found // send found to channel
}

func main() {
slice := []int{1, 7, 8, -9, 12, -1, 13, -7}
lookForValue := 8

resultChan := make(chan bool)
go exist(slice[:len(slice)/2], lookForValue, resultChan)
go exist(slice[len(slice)/2:], lookForValue, resultChan)
firstHalf, secondHalf := <-resultChan, <-resultChan // receive 2 values from channel

fmt.Println(firstHalf || secondHalf)
}
```

## Buffered Channels

Channels default to being unbuffered, meaning they will only accept sends if a receiver is ready to receive.
But channels can be buffered.
Provide the buffer length as the second argument to make to initialize a buffered channel:

```go
ch := make(chan int, 150)
```

Channel will be blocked when the buffer is full and receives will block when the buffer is empty.

## Range and Close

A sender can `close` a channel to indicate that no more values will be sent.
Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression: after

```go
v, ok := <-ch
```

`ok` is `false` if there are no more values to receive and the channel is closed.

The loop `for i := range c` receives values from the channel repeatedly until it is closed.[^1]

### Notes[^1]:
~~~~exercism/note
Only the sender should close a channel, never the receiver. Sending on a closed channel will cause a panic.
~~~~

~~~~exercism/note
Channels aren't like files; you don't usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.
~~~~

## Select

The `select` statement like switch-case in other languages, but for channels.
A `select` blocks until one of its cases can run, then it executes that case.
Order of run cases is random.

### Example
We want to brute force to find password of a lock, we use goroutines and channels to do this.

```go
package main

import "fmt"

func bruteForce(ch1, ch2, ch3 chan int) {
lockPassword := 123456
for {
select {
case x := <-ch1:
if x == lockPassword {
fmt.Println("Password found on channel 1:", x)
return
}
case x := <-ch2:
if x == lockPassword {
fmt.Println("Password found on channel 2:", x)
return
}
case x := <-ch3:
if x == lockPassword {
fmt.Println("Password found on channel 3:", x)
return
}
}
}
}

func main() {
ch1 := make(chan int)
ch2 := make(chan int)
ch3 := make(chan int)
go func() {
for i := 0; i < 100000000; i++ {
if i%3 == 1 {
ch1 <- i
} else if i%3 == 2 {
ch2 <- i
} else {
ch3 <- i
}
}
}()
bruteForce(ch1, ch2, ch3)
}
```

[^1]: [Range and Close](https://go.dev/tour/concurrency/4)
138 changes: 138 additions & 0 deletions concepts/channels/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Introduction

[Goroutines](https://go.dev/tour/concurrency/1) are a way to execute concurrent code in Go.
Goroutines are works independently, if they need to communicate, Go preferred to use channels.
Create a new channel with `make(chan val-type)`.
Channels are typed by the values they convey.

```go
ch := make(chan int) // create new channel
ch <- value // Send value(same as channel type) to channel ch.
value := <-ch // Receive from ch and assign value to value variable.
```

By default, sends and receives block wait until the other side is ready.
This allows goroutines to synchronize without explicit locks or condition variables.

Example of channel usage, if we want to findout the value exists in the slice, we can split the slice into two parts, find separately in other goroutines, and then send the result to the main goroutine.

```go
package main

import "fmt"

func exist(slice []int, x int, resultChan chan bool) {
found := false
for _, v := range slice {
if v == x {
found = true
break
}
}
resultChan <- found // send found to channel
}

func main() {
slice := []int{1, 7, 8, -9, 12, -1, 13, -7}
lookForValue := 8

resultChan := make(chan bool)
go exist(slice[:len(slice)/2], lookForValue, resultChan)
go exist(slice[len(slice)/2:], lookForValue, resultChan)
firstHalf, secondHalf := <-resultChan, <-resultChan // receive 2 values from channel

fmt.Println(firstHalf || secondHalf)
}
```

## Buffered Channels

Channels default to being unbuffered, meaning they will only accept sends if a receiver is ready to receive.
But channels can be buffered.
Provide the buffer length as the second argument to make to initialize a buffered channel:

```go
ch := make(chan int, 150)
```

Channel will be blocked when the buffer is full and receives will block when the buffer is empty.

## Range and Close

A sender can `close` a channel to indicate that no more values will be sent.
Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression: after

```go
v, ok := <-ch
```

`ok` is `false` if there are no more values to receive and the channel is closed.

The loop `for i := range c` receives values from the channel repeatedly until it is closed.[^1]

### Notes[^1]:
~~~~exercism/note
Only the sender should close a channel, never the receiver. Sending on a closed channel will cause a panic.
~~~~

~~~~exercism/note
Channels aren't like files; you don't usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.
~~~~

## Select

The `select` statement like switch-case in other languages, but for channels.
A `select` blocks until one of its cases can run, then it executes that case.
Order of run cases is random.

### Example
We want to brute force to find password of a lock, we use goroutines and channels to do this.

```go
package main

import "fmt"

func bruteForce(ch1, ch2, ch3 chan int) {
lockPassword := 123456
for {
select {
case x := <-ch1:
if x == lockPassword {
fmt.Println("Password found on channel 1:", x)
return
}
case x := <-ch2:
if x == lockPassword {
fmt.Println("Password found on channel 2:", x)
return
}
case x := <-ch3:
if x == lockPassword {
fmt.Println("Password found on channel 3:", x)
return
}
}
}
}

func main() {
ch1 := make(chan int)
ch2 := make(chan int)
ch3 := make(chan int)
go func() {
for i := 0; i < 100000000; i++ {
if i%3 == 1 {
ch1 <- i
} else if i%3 == 2 {
ch2 <- i
} else {
ch3 <- i
}
}
}()
bruteForce(ch1, ch2, ch3)
}
```

[^1]: [Range and Close](https://go.dev/tour/concurrency/4)
22 changes: 22 additions & 0 deletions concepts/channels/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
{
"url": "https://go.dev/tour/concurrency/2",
"description": "Tour of Go: Channels"
},
{
"url": "https://go.dev/tour/concurrency/3",
"description": "Tour of Go: Buffered Channels"
},
{
"url": "https://go.dev/tour/concurrency/4",
"description": "Tour of Go: Range and Close"
},
{
"url": "https://go.dev/tour/concurrency/5",
"description": "Tour of Go: Select"
},
{
"url": "https://gobyexample.com/channels",
"description": "Go by Example: Channels"
}
]
19 changes: 19 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,20 @@
"structs"
],
"status": "beta"
},
{
"slug": "cache",
"name": "Cache",
"uuid": "0af3bc2d-8c0c-43d2-ba5f-c971e5d70bb5",
"concepts": [
"channels"
],
"prerequisites": [
"functions",
"for-loops",
"range-iteration"
],
"status": "beta"
}
],
"practice": [
Expand Down Expand Up @@ -2150,6 +2164,11 @@
"name": "Variadic Functions",
"slug": "variadic-functions",
"uuid": "7c9ad69f-b28a-4366-affc-870fc48e51eb"
},
{
"name": "Channels",
"slug": "channels",
"uuid": "7ec81c14-3eb6-4ebc-bf74-1a339b4b5851"
}
],
"key_features": [
Expand Down
2 changes: 2 additions & 0 deletions exercises/concept/cache/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Hints

33 changes: 33 additions & 0 deletions exercises/concept/cache/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Instructions

In this exercise you'll be writing code to handle multilayer cache with Go channels.
Cache layer defines as a struct blow:

```go
type Cache struct {
ResponseTime time.Duration
Data map[string]string
}
```

As you see, the `ResponseTime` is the time it takes to get(or set) the value from(to) the cache, and the `Data` is the data stored in the cache.

You have two tasks for reading and writing data form(to) cache layers.

## 1. Get value from fastest layer

We have multiple layers of cache, and we want to get the value from the fastest layer that has the key we want. the layers of cache given to you as `cacheLayers` parameter.
You have to get a value from the layer have the value for this key as fast as possible and return it with `nil` error or return `ErrKeyNotExist` error if the key does not exist in any layer.


```go
GetValueFromFastestLayer(cacheLayers []Cache, key string) (string, error)
```

## 2. Set value to all layers
The key and value are given to you as `key` and `value` parameters.
You have to set the value to all `cacheLayers` as fast as possible, you have maximum `max(cacheLayers.ResponseTime)` to set the value to all layers.

```go
SetValueToAllLayers(cacheLayers []Cache, key string, value string) error
```
Loading

0 comments on commit 891e190

Please sign in to comment.