-
-
Notifications
You must be signed in to change notification settings - Fork 654
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add channels concept #2324
Add channels concept #2324
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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": [] | ||
} |
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 | ||
junedev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) |
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) |
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" | ||
} | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed, please change the status to "wip" for now. |
||
} | ||
], | ||
"practice": [ | ||
|
@@ -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": [ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Hints | ||
|
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When you say "multilayer cache", I imagine something like what is usually called "multi-level cache" which would mean you would check different cache levels in a fixed order and the deeper in the level you go the longer it takes to retrieve the data. The issue is that in such a scenario, you would always make the request sequentially and only proceed to the next level/layer when no result was returned. The word "layer" puts emphasis on this, layers are usually passed/pealed one by one. It might be less confusing to only say there are multiple caches available or talk about a cluster of caches (although the later also has its existing meaning which is different from the what you want to do in the exercise). In any case, add one or two more sentences explaining what you mean by whatever term you decide to use to avoid misunderstandings. |
||
Cache layer defines as a struct blow: | ||
|
||
```go | ||
type Cache struct { | ||
ResponseTime time.Duration | ||
Data map[string]string | ||
} | ||
Comment on lines
+7
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was wondering why this is presented in the instructions. It feels like this an implementation detail of the tests and it would be enough if the instructions would only state what the cache interface looks like (has a Get and Set method). Then the tests can provide whatever implementation for that interface. The internals of the cache should not matter for solving the exercise. You only need to say something like "some caches are faster to respond than others". Showing this cache structs might make the student think they can solve the exercise by simply checking the value of "ResponseTime". |
||
``` | ||
|
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code here should not present the function signature, instead if should show an example of how the function is called/used (and a potential result). You don't have to include the definition of the cache structs in here though. Same for the other code block below. |
||
``` | ||
|
||
## 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm struggling with this sentence as using the channels will have some overhead so you won't exactly hit the max response time. Maybe you can rephrase it a bit to say something like "Try to get as close as possible to the response time of the slowest cache." |
||
|
||
```go | ||
SetValueToAllLayers(cacheLayers []Cache, key string, value string) error | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please ensure some general points are taken care of throughout the content: