This commit is contained in:
wagslane
2023-05-01 15:25:27 -06:00
parent f8912668b8
commit 9be3074de6
868 changed files with 58698 additions and 2 deletions

View File

@@ -0,0 +1,57 @@
package main
import (
"fmt"
"time"
)
func pingPong(numPings int) {
pings := make(chan struct{})
pongs := make(chan struct{})
go ponger(pings, pongs)
go pinger(pings, pongs, numPings)
}
// TEST SUITE - Don't touch below this line
func pinger(pings, pongs chan struct{}, numPings int) {
go func() {
sleepTime := 50 * time.Millisecond
for i := 0; i < numPings; i++ {
fmt.Println("ping", i, "sent")
pings <- struct{}{}
time.Sleep(sleepTime)
sleepTime *= 2
}
close(pings)
}()
i := 0
for range pongs {
fmt.Println("pong", i, "got")
i++
}
fmt.Println("pongs done")
}
func ponger(pings, pongs chan struct{}) {
i := 0
for range pings {
fmt.Println("ping", i, "got", "pong", i, "sent")
pongs <- struct{}{}
i++
}
fmt.Println("pings done")
close(pongs)
}
func test(numPings int) {
fmt.Println("Starting game...")
pingPong(numPings)
fmt.Println("===== Game over =====")
}
func main() {
test(4)
test(3)
test(2)
}

View File

@@ -0,0 +1,57 @@
package main
import (
"fmt"
"time"
)
func pingPong(numPings int) {
pings := make(chan struct{})
pongs := make(chan struct{})
go ponger(pings, pongs)
pinger(pings, pongs, numPings)
}
// TEST SUITE - Don't touch below this line
func pinger(pings, pongs chan struct{}, numPings int) {
go func() {
sleepTime := 50 * time.Millisecond
for i := 0; i < numPings; i++ {
fmt.Println("ping", i, "sent")
pings <- struct{}{}
time.Sleep(sleepTime)
sleepTime *= 2
}
close(pings)
}()
i := 0
for range pongs {
fmt.Println("pong", i, "got")
i++
}
fmt.Println("pongs done")
}
func ponger(pings, pongs chan struct{}) {
i := 0
for range pings {
fmt.Println("ping", i, "got", "pong", i, "sent")
pongs <- struct{}{}
i++
}
fmt.Println("pings done")
close(pongs)
}
func test(numPings int) {
fmt.Println("Starting game...")
pingPong(numPings)
fmt.Println("===== Game over =====")
}
func main() {
test(4)
test(3)
test(2)
}

View File

@@ -0,0 +1,39 @@
Starting game...
ping 0 sent
ping 0 got pong 0 sent
pong 0 got
ping 1 sent
ping 1 got pong 1 sent
pong 1 got
ping 2 sent
ping 2 got pong 2 sent
pong 2 got
ping 3 sent
ping 3 got pong 3 sent
pong 3 got
pings done
pongs done
===== Game over =====
Starting game...
ping 0 sent
ping 0 got pong 0 sent
pong 0 got
ping 1 sent
ping 1 got pong 1 sent
pong 1 got
ping 2 sent
ping 2 got pong 2 sent
pong 2 got
pings done
pongs done
===== Game over =====
Starting game...
ping 0 sent
ping 0 got pong 0 sent
pong 0 got
ping 1 sent
ping 1 got pong 1 sent
pong 1 got
pings done
pongs done
===== Game over =====

View File

@@ -0,0 +1,7 @@
# Ping Pong
Many tech companies play ping pong in the office during break time. At Mailio, we play virtual ping pong using channels.
## Assignment
Fix the bug in the `pingPong` function. It shouldn't `return` until the entire game of ping pong is complete.

View File

@@ -0,0 +1,28 @@
package main
import (
"fmt"
"time"
)
func sendEmail(message string) {
func() {
time.Sleep(time.Millisecond * 250)
fmt.Printf("Email received: '%s'\n", message)
}()
fmt.Printf("Email sent: '%s'\n", message)
}
// Don't touch below this line
func test(message string) {
sendEmail(message)
time.Sleep(time.Millisecond * 500)
fmt.Println("========================")
}
func main() {
test("Hello there Stacy!")
test("Hi there John!")
test("Hey there Jane!")
}

View File

@@ -0,0 +1,28 @@
package main
import (
"fmt"
"time"
)
func sendEmail(message string) {
go func() {
time.Sleep(time.Millisecond * 250)
fmt.Printf("Email received: '%s'\n", message)
}()
fmt.Printf("Email sent: '%s'\n", message)
}
// Don't touch below this line
func test(message string) {
sendEmail(message)
time.Sleep(time.Millisecond * 500)
fmt.Println("========================")
}
func main() {
test("Hello there Stacy!")
test("Hi there John!")
test("Hey there Jane!")
}

View File

@@ -0,0 +1,9 @@
Email sent: 'Hello there Stacy!'
Email received: 'Hello there Stacy!'
========================
Email sent: 'Hi there John!'
Email received: 'Hi there John!'
========================
Email sent: 'Hey there Jane!'
Email received: 'Hey there Jane!'
========================

View File

@@ -0,0 +1,29 @@
# Concurrency
## What is concurrency?
Concurrency is the ability to perform multiple tasks at the same time. Typically, our code is executed one line at a time, one after the other. This is called *sequential execution* or *synchronous execution*.
![concurrency](https://i.imgur.com/1pQKFgw.png)
If the computer we're running our code on has multiple cores, we can even execute multiple tasks at *exactly* the same time. If we're running on a single core, a single code executes code at *almost* the same time by switching between tasks very quickly. Either way, the code we write looks the same in Go and takes advantage of whatever resources are available.
## How does concurrency work in Go?
Go was designed to be concurrent, which is a trait *fairly* unique to Go. It excels at performing many tasks simultaneously safely using a simple syntax.
There isn't a popular programming language in existence where spawning concurrent execution is quite as elegant, at least in my opinion.
Concurrency is as simple as using the `go` keyword when calling a function:
```go
go doSomething()
```
In the example above, `doSomething()` will be executed concurrently with the rest of the code in the function. The `go` keyword is used to spawn a new *[goroutine](https://gobyexample.com/goroutines)*.
## Assignment
At Mailio we send *a lot* of network requests. Each email we send must go out over the internet. To serve our millions of customers, we need a single Go program to be capable of sending *thousands* of emails at once.
Edit the `sendEmail()` function to execute its anonymous function concurrently so that the "received" message prints *after* the "sent" message.

View File

@@ -0,0 +1,96 @@
package main
import (
"fmt"
"time"
)
func filterOldEmails(emails []email) {
isOldChan := make(chan bool)
for _, e := range emails {
if e.date.Before(time.Date(2020, 0, 0, 0, 0, 0, 0, time.UTC)) {
isOldChan <- true
continue
}
isOldChan <- false
}
isOld := <-isOldChan
fmt.Println("email 1 is old:", isOld)
isOld = <-isOldChan
fmt.Println("email 2 is old:", isOld)
isOld = <-isOldChan
fmt.Println("email 3 is old:", isOld)
}
// TEST SUITE -- Don't touch below this line
type email struct {
body string
date time.Time
}
func test(emails []email) {
filterOldEmails(emails)
fmt.Println("==========================================")
}
func main() {
test([]email{
{
body: "Are you going to make it?",
date: time.Date(2019, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "I need a break",
date: time.Date(2021, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "What were you thinking?",
date: time.Date(2022, 0, 0, 0, 0, 0, 0, time.UTC),
},
})
test([]email{
{
body: "Yo are you okay?",
date: time.Date(2018, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "Have you heard of that website Boot.dev?",
date: time.Date(2017, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "It's awesome honestly.",
date: time.Date(2016, 0, 0, 0, 0, 0, 0, time.UTC),
},
})
test([]email{
{
body: "Today is the day!",
date: time.Date(2019, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "What do you want for lunch?",
date: time.Date(2021, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "Why are you the way that you are?",
date: time.Date(2022, 0, 0, 0, 0, 0, 0, time.UTC),
},
})
test([]email{
{
body: "Did we do it?",
date: time.Date(2019, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "Letsa Go!",
date: time.Date(2021, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "Okay...?",
date: time.Date(2022, 0, 0, 0, 0, 0, 0, time.UTC),
},
})
}

View File

@@ -0,0 +1,98 @@
package main
import (
"fmt"
"time"
)
func filterOldEmails(emails []email) {
isOldChan := make(chan bool)
go func() {
for _, e := range emails {
if e.date.Before(time.Date(2020, 0, 0, 0, 0, 0, 0, time.UTC)) {
isOldChan <- true
continue
}
isOldChan <- false
}
}()
isOld := <-isOldChan
fmt.Println("email 1 is old:", isOld)
isOld = <-isOldChan
fmt.Println("email 2 is old:", isOld)
isOld = <-isOldChan
fmt.Println("email 3 is old:", isOld)
}
// TEST SUITE -- Don't touch below this line
type email struct {
body string
date time.Time
}
func test(emails []email) {
filterOldEmails(emails)
fmt.Println("==========================================")
}
func main() {
test([]email{
{
body: "Are you going to make it?",
date: time.Date(2019, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "I need a break",
date: time.Date(2021, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "What were you thinking?",
date: time.Date(2022, 0, 0, 0, 0, 0, 0, time.UTC),
},
})
test([]email{
{
body: "Yo are you okay?",
date: time.Date(2018, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "Have you heard of that website Boot.dev?",
date: time.Date(2017, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "It's awesome honestly.",
date: time.Date(2016, 0, 0, 0, 0, 0, 0, time.UTC),
},
})
test([]email{
{
body: "Today is the day!",
date: time.Date(2019, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "What do you want for lunch?",
date: time.Date(2021, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "Why are you the way that you are?",
date: time.Date(2022, 0, 0, 0, 0, 0, 0, time.UTC),
},
})
test([]email{
{
body: "Did we do it?",
date: time.Date(2019, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "Letsa Go!",
date: time.Date(2021, 0, 0, 0, 0, 0, 0, time.UTC),
},
{
body: "Okay...?",
date: time.Date(2022, 0, 0, 0, 0, 0, 0, time.UTC),
},
})
}

View File

@@ -0,0 +1,16 @@
email 1 is old: true
email 2 is old: false
email 3 is old: false
==========================================
email 1 is old: true
email 2 is old: true
email 3 is old: true
==========================================
email 1 is old: true
email 2 is old: false
email 3 is old: false
==========================================
email 1 is old: true
email 2 is old: false
email 3 is old: false
==========================================

View File

@@ -0,0 +1,37 @@
# Channels
Channels are a typed, thread-safe queue. Channels allow different goroutines to communicate with each other.
## Create a channel
Like maps and slices, channels must be created *before* use. They also use the same `make` keyword:
```go
ch := make(chan int)
```
## Send data to a channel
```go
ch <- 69
```
The `<-` operator is called the *channel operator*. Data flows in the direction of the arrow. This operation will *block* until another goroutine is ready to receive the value.
## Receive data from a channel
```go
v := <-ch
```
This reads and removes a value from the channel and saves it into the variable `v`. This operation will *block* until there is a value in the channel to be read.
## Blocking and deadlocks
A [deadlock](https://yourbasic.org/golang/detect-deadlock/#:~:text=yourbasic.org%2Fgolang,look%20at%20this%20simple%20example.) is when a group of goroutines are all blocking so none of them can continue. This is a common bug that you need to watch out for in concurrent programming.
## Assignment
Run the program. You'll see that it deadlocks and never exits. The `filterOldEmails` function is trying to send on a channel and there is no other goroutine running that can accept the value *from* the channel.
Fix the deadlock by spawning an anonymous goroutine to write to the `isOldChan` channel instead of using the same goroutine that's reading from it.

View File

@@ -0,0 +1,38 @@
package main
import (
"fmt"
"time"
)
func waitForDbs(numDBs int, dbChan chan struct{}) {
// ?
}
// don't touch below this line
func test(numDBs int) {
dbChan := getDatabasesChannel(numDBs)
fmt.Printf("Waiting for %v databases...\n", numDBs)
waitForDbs(numDBs, dbChan)
time.Sleep(time.Millisecond * 10) // ensure the last print statement happens
fmt.Println("All databases are online!")
fmt.Println("=====================================")
}
func main() {
test(3)
test(4)
test(5)
}
func getDatabasesChannel(numDBs int) chan struct{} {
ch := make(chan struct{})
go func() {
for i := 0; i < numDBs; i++ {
ch <- struct{}{}
fmt.Printf("Database %v is online\n", i+1)
}
}()
return ch
}

View File

@@ -0,0 +1,40 @@
package main
import (
"fmt"
"time"
)
func waitForDbs(numDBs int, dbChan chan struct{}) {
for i := 0; i < numDBs; i++ {
<-dbChan
}
}
// don't touch below this line
func test(numDBs int) {
dbChan := getDatabasesChannel(numDBs)
fmt.Printf("Waiting for %v databases...\n", numDBs)
waitForDbs(numDBs, dbChan)
time.Sleep(time.Millisecond * 10) // ensure the last print statement happens
fmt.Println("All databases are online!")
fmt.Println("=====================================")
}
func main() {
test(3)
test(4)
test(5)
}
func getDatabasesChannel(numDBs int) chan struct{} {
ch := make(chan struct{})
go func() {
for i := 0; i < numDBs; i++ {
ch <- struct{}{}
fmt.Printf("Database %v is online\n", i+1)
}
}()
return ch
}

View File

@@ -0,0 +1,21 @@
Waiting for 3 databases...
Database 1 is online
Database 2 is online
Database 3 is online
All databases are online!
=====================================
Waiting for 4 databases...
Database 1 is online
Database 2 is online
Database 3 is online
Database 4 is online
All databases are online!
=====================================
Waiting for 5 databases...
Database 1 is online
Database 2 is online
Database 3 is online
Database 4 is online
Database 5 is online
All databases are online!
=====================================

View File

@@ -0,0 +1,17 @@
# Channels
Empty structs are often used as `tokens` in Go programs. In this context, a token is a [unary](https://en.wikipedia.org/wiki/Unary_operation) value. In other words, we don't care *what* is passed through the channel. We care *when* and *if* it is passed.
We can block and wait until *something* is sent on a channel using the following syntax
```go
<-ch
```
This will block until it pops a single item off the channel, then continue, discarding the item.
## Assignment
Our Mailio server isn't able to boot up until it receives the signal that its databases are all online, and it learns about them being online by waiting for tokens (empty structs) on a channel.
Complete the `waitForDbs` function. It should block until it receives `numDBs` tokens on the `dbChan` channel. Each time it reads a token the `getDatabasesChannel` goroutine will print a message to the console for you.

View File

@@ -0,0 +1,34 @@
package main
import "fmt"
func addEmailsToQueue(emails []string) chan string {
emailsToSend := make(chan string)
for _, email := range emails {
emailsToSend <- email
}
return emailsToSend
}
// TEST SUITE - Don't Touch Below This Line
func sendEmails(batchSize int, ch chan string) {
for i := 0; i < batchSize; i++ {
email := <-ch
fmt.Println("Sending email:", email)
}
}
func test(emails ...string) {
fmt.Printf("Adding %v emails to queue...\n", len(emails))
ch := addEmailsToQueue(emails)
fmt.Println("Sending emails...")
sendEmails(len(emails), ch)
fmt.Println("==========================================")
}
func main() {
test("Hello John, tell Kathy I said hi", "Whazzup bruther")
test("I find that hard to believe.", "When? I don't know if I can", "What time are you thinking?")
test("She says hi!", "Yeah its tomorrow. So we're good.", "Cool see you then!", "Bye!")
}

View File

@@ -0,0 +1,34 @@
package main
import "fmt"
func addEmailsToQueue(emails []string) chan string {
emailsToSend := make(chan string, len(emails))
for _, email := range emails {
emailsToSend <- email
}
return emailsToSend
}
// TEST SUITE - Don't Touch Below This Line
func sendEmails(batchSize int, ch chan string) {
for i := 0; i < batchSize; i++ {
email := <-ch
fmt.Println("Sending email:", email)
}
}
func test(emails ...string) {
fmt.Printf("Adding %v emails to queue...\n", len(emails))
ch := addEmailsToQueue(emails)
fmt.Println("Sending emails...")
sendEmails(len(emails), ch)
fmt.Println("==========================================")
}
func main() {
test("Hello John, tell Kathy I said hi", "Whazzup bruther")
test("I find that hard to believe.", "When? I don't know if I can", "What time are you thinking?")
test("She says hi!", "Yeah its tomorrow. So we're good.", "Cool see you then!", "Bye!")
}

View File

@@ -0,0 +1,18 @@
Adding 2 emails to queue...
Sending emails...
Sending email: Hello John, tell Kathy I said hi
Sending email: Whazzup bruther
==========================================
Adding 3 emails to queue...
Sending emails...
Sending email: I find that hard to believe.
Sending email: When? I don't know if I can
Sending email: What time are you thinking?
==========================================
Adding 4 emails to queue...
Sending emails...
Sending email: She says hi!
Sending email: Yeah its tomorrow. So we're good.
Sending email: Cool see you then!
Sending email: Bye!
==========================================

View File

@@ -0,0 +1,21 @@
# Buffered Channels
Channels can *optionally* be buffered.
## Creating a channel with a buffer
You can provide a buffer length as the second argument to `make()` to create a buffered channel:
```go
ch := make(chan int, 100)
```
Sending on a buffered channel only blocks when the buffer is *full*.
Receiving blocks only when the buffer is *empty*.
## Assignment
We want to be able to send emails in *batches*. A *writing* goroutine will write an entire batch of email messages to a buffered channel, and later, once the channel is full, a *reading* goroutine will read all of the messages from the channel and send them out to our clients.
Complete the `addEmailsToQueue` function. It should create a buffered channel with a buffer large enough to store all of the `emails` it's given. It should then write the emails to the channel in order, and finally return the channel.

View File

@@ -0,0 +1,39 @@
package main
import (
"fmt"
"time"
)
func countReports(numSentCh chan int) int {
// ?
}
// TEST SUITE - Don't touch below this line
func test(numBatches int) {
numSentCh := make(chan int)
go sendReports(numBatches, numSentCh)
fmt.Println("Start counting...")
numReports := countReports(numSentCh)
fmt.Printf("%v reports sent!\n", numReports)
fmt.Println("========================")
}
func main() {
test(3)
test(4)
test(5)
test(6)
}
func sendReports(numBatches int, ch chan int) {
for i := 0; i < numBatches; i++ {
numReports := i*23 + 32%17
ch <- numReports
fmt.Printf("Sent batch of %v reports\n", numReports)
time.Sleep(time.Millisecond * 100)
}
close(ch)
}

View File

@@ -0,0 +1,47 @@
package main
import (
"fmt"
"time"
)
func countReports(numSentCh chan int) int {
total := 0
for {
numSent, ok := <-numSentCh
if !ok {
break
}
total += numSent
}
return total
}
// TEST SUITE - Don't touch below this line
func test(numBatches int) {
numSentCh := make(chan int)
go sendReports(numBatches, numSentCh)
fmt.Println("Start counting...")
numReports := countReports(numSentCh)
fmt.Printf("%v reports sent!\n", numReports)
fmt.Println("========================")
}
func main() {
test(3)
test(4)
test(5)
test(6)
}
func sendReports(numBatches int, ch chan int) {
for i := 0; i < numBatches; i++ {
numReports := i*23 + 32%17
ch <- numReports
fmt.Printf("Sent batch of %v reports\n", numReports)
time.Sleep(time.Millisecond * 100)
}
close(ch)
}

View File

@@ -0,0 +1,30 @@
Start counting...
Sent batch of 15 reports
Sent batch of 38 reports
Sent batch of 61 reports
114 reports sent!
========================
Start counting...
Sent batch of 15 reports
Sent batch of 38 reports
Sent batch of 61 reports
Sent batch of 84 reports
198 reports sent!
========================
Start counting...
Sent batch of 15 reports
Sent batch of 38 reports
Sent batch of 61 reports
Sent batch of 84 reports
Sent batch of 107 reports
305 reports sent!
========================
Start counting...
Sent batch of 15 reports
Sent batch of 38 reports
Sent batch of 61 reports
Sent batch of 84 reports
Sent batch of 107 reports
Sent batch of 130 reports
435 reports sent!
========================

View File

@@ -0,0 +1,40 @@
# Closing channels in Go
Channels can be explicitly closed by a *sender*:
```go
ch := make(chan int)
// do some stuff with the channel
close(ch)
```
## Checking if a channel is closed
Similar to the `ok` value when accessing data in a `map`, receivers can check the `ok` value when receiving from a channel to test if a channel was closed.
```go
v, ok := <-ch
```
ok is `false` if the channel is empty and closed.
## Don't send on a closed channel
Sending on a closed channel will cause a panic. A panic on the main goroutine will cause the entire program to crash, and a panic in any other goroutine will cause *that goroutine* to crash.
Closing isn't necessary. There's nothing wrong with leaving channels open, they'll still be garbage collected if they're unused. You should close channels to indicate explicitly to a receiver that nothing else is going to come across.
## Assignment
At Mailio we're all about keeping track of what our systems are up to with great logging and [telemetry](https://en.wikipedia.org/wiki/Telemetry).
The `sendReports` function sends out a batch of reports to our clients and reports back how many were sent across a channel. It closes the channel when it's done.
Complete the `countReports` function. It should:
* Use an infinite `for` loop to read from the channel:
* If the channel is closed, break out of the loop
* Otherwise, keep a running total of the number of reports sent
* Return the total number of reports sent

View File

@@ -0,0 +1,35 @@
package main
import (
"fmt"
"time"
)
func concurrrentFib(n int) {
// ?
}
// TEST SUITE - Don't touch below this line
func test(n int) {
fmt.Printf("Printing %v numbers...\n", n)
concurrrentFib(n)
fmt.Println("==============================")
}
func main() {
test(10)
test(5)
test(20)
test(13)
}
func fibonacci(n int, ch chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
ch <- x
x, y = y, x+y
time.Sleep(time.Millisecond * 10)
}
close(ch)
}

View File

@@ -0,0 +1,40 @@
package main
import (
"fmt"
"time"
)
func concurrrentFib(n int) {
ch := make(chan int)
go fibonacci(n, ch)
for v := range ch {
fmt.Println(v)
}
}
// TEST SUITE - Don't touch below this line
func test(n int) {
fmt.Printf("Printing %v numbers...\n", n)
concurrrentFib(n)
fmt.Println("==============================")
}
func main() {
test(10)
test(5)
test(20)
test(13)
}
func fibonacci(n int, ch chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
ch <- x
x, y = y, x+y
time.Sleep(time.Millisecond * 10)
}
close(ch)
}

View File

@@ -0,0 +1,56 @@
Printing 10 numbers...
0
1
1
2
3
5
8
13
21
34
==============================
Printing 5 numbers...
0
1
1
2
3
==============================
Printing 20 numbers...
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
==============================
Printing 13 numbers...
0
1
1
2
3
5
8
13
21
34
55
89
144
==============================

View File

@@ -0,0 +1,21 @@
# Range
Similar to slices and maps, channels can be ranged over.
```go
for item := range ch {
// item is the next value received from the channel
}
```
This example will receive values over the channel (blocking at each iteration if nothing new is there) and will exit only when the channel is closed.
## Assignment
It's that time again, Mailio is hiring and we've been assigned to do the interview. For some reason, the [Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_number) is Mailio's interview problem of choice. We've been tasked with building a small toy program we can use in the interview.
Complete the `concurrrentFib` function. It should:
* Create a new channel of `int`s
* Call `fibonacci` in a goroutine, passing it the channel and the number of Fibonacci numbers to generate, `n`
* Use a `range` loop to read from the channel and print out the numbers one by one, each on a new line

View File

@@ -0,0 +1,90 @@
package main
import (
"fmt"
"math/rand"
"time"
)
func logMessages(chEmails, chSms chan string) {
// ?
}
// TEST SUITE - Don't touch below this line
func logSms(sms string) {
fmt.Println("SMS:", sms)
}
func logEmail(email string) {
fmt.Println("Email:", email)
}
func test(sms []string, emails []string) {
fmt.Println("Starting...")
chSms, chEmails := sendToLogger(sms, emails)
logMessages(chEmails, chSms)
fmt.Println("===============================")
}
func main() {
rand.Seed(0)
test(
[]string{
"hi friend",
"What's going on?",
"Welcome to the business",
"I'll pay you to be my friend",
},
[]string{
"Will you make your appointment?",
"Let's be friends",
"What are you doing?",
"I can't believe you've done this.",
},
)
test(
[]string{
"this song slaps hard",
"yooo hoooo",
"i'm a big fan",
},
[]string{
"What do you think of this song?",
"I hate this band",
"Can you believe this song?",
},
)
}
func sendToLogger(sms, emails []string) (chSms, chEmails chan string) {
chSms = make(chan string)
chEmails = make(chan string)
go func() {
for i := 0; i < len(sms) && i < len(emails); i++ {
done := make(chan struct{})
s := sms[i]
e := emails[i]
t1 := time.Millisecond * time.Duration(rand.Intn(1000))
t2 := time.Millisecond * time.Duration(rand.Intn(1000))
go func() {
time.Sleep(t1)
chSms <- s
done <- struct{}{}
}()
go func() {
time.Sleep(t2)
chEmails <- e
done <- struct{}{}
}()
<-done
<-done
time.Sleep(10 * time.Millisecond)
}
close(chSms)
close(chEmails)
}()
return chSms, chEmails
}

View File

@@ -0,0 +1,103 @@
package main
import (
"fmt"
"math/rand"
"time"
)
func logMessages(chEmails, chSms chan string) {
for {
select {
case email, ok := <-chEmails:
if !ok {
return
}
logEmail(email)
case sms, ok := <-chSms:
if !ok {
return
}
logSms(sms)
}
}
}
// TEST SUITE - Don't touch below this line
func logSms(sms string) {
fmt.Println("SMS:", sms)
}
func logEmail(email string) {
fmt.Println("Email:", email)
}
func test(sms []string, emails []string) {
fmt.Println("Starting...")
chSms, chEmails := sendToLogger(sms, emails)
logMessages(chEmails, chSms)
fmt.Println("===============================")
}
func main() {
rand.Seed(0)
test(
[]string{
"hi friend",
"What's going on?",
"Welcome to the business",
"I'll pay you to be my friend",
},
[]string{
"Will you make your appointment?",
"Let's be friends",
"What are you doing?",
"I can't believe you've done this.",
},
)
test(
[]string{
"this song slaps hard",
"yooo hoooo",
"i'm a big fan",
},
[]string{
"What do you think of this song?",
"I hate this band",
"Can you believe this song?",
},
)
}
func sendToLogger(sms, emails []string) (chSms, chEmails chan string) {
chSms = make(chan string)
chEmails = make(chan string)
go func() {
for i := 0; i < len(sms) && i < len(emails); i++ {
done := make(chan struct{})
s := sms[i]
e := emails[i]
t1 := time.Millisecond * time.Duration(rand.Intn(1000))
t2 := time.Millisecond * time.Duration(rand.Intn(1000))
go func() {
time.Sleep(t1)
chSms <- s
done <- struct{}{}
}()
go func() {
time.Sleep(t2)
chEmails <- e
done <- struct{}{}
}()
<-done
<-done
time.Sleep(10 * time.Millisecond)
}
close(chSms)
close(chEmails)
}()
return chSms, chEmails
}

View File

@@ -0,0 +1,18 @@
Starting...
SMS: hi friend
Email: Will you make your appointment?
SMS: What's going on?
Email: Let's be friends
Email: What are you doing?
SMS: Welcome to the business
Email: I can't believe you've done this.
SMS: I'll pay you to be my friend
===============================
Starting...
SMS: this song slaps hard
Email: What do you think of this song?
Email: I hate this band
SMS: yooo hoooo
SMS: i'm a big fan
Email: Can you believe this song?
===============================

View File

@@ -0,0 +1,24 @@
# Select
Sometimes we have a single goroutine listening to multiple channels and want to process data in the order it comes through each channel.
A `select` statement is used to listen to multiple channels at the same time. It is similar to a `switch` statement but for channels.
```go
select {
case i, ok := <- chInts:
fmt.Println(i)
case s, ok := <- chStrings:
fmt.Println(s)
}
```
The first channel with a value ready to be received will fire and its body will execute. If multiple channels are ready at the same time one is chosen randomly. The `ok` variable in the example above refers to whether or not the channel has been closed by the sender yet.
## Assignment
Complete the `logMessages` function.
Use an infinite `for` loop and a select statement to log the emails and sms messages as they come in order across the two channels. Add a condition to `return` from the function when *one* of the two channels closes, whichever is first.
Use the `logSms` and `logEmail` functions to log the messages.

View File

@@ -0,0 +1,35 @@
package main
import (
"fmt"
"time"
)
func saveBackups(snapshotTicker, saveAfter <-chan time.Time) {
// ?
}
// TEST SUITE - Don't touch below this line
func takeSnapshot() {
fmt.Println("Taking a backup snapshot...")
}
func saveSnapshot() {
fmt.Println("All backups saved!")
}
func waitForData() {
fmt.Println("Nothing to do, waiting...")
}
func test() {
snapshotTicker := time.Tick(800 * time.Millisecond)
saveAfter := time.After(2800 * time.Millisecond)
saveBackups(snapshotTicker, saveAfter)
fmt.Println("===========================")
}
func main() {
test()
}

View File

@@ -0,0 +1,46 @@
package main
import (
"fmt"
"time"
)
func saveBackups(snapshotTicker, saveAfter <-chan time.Time) {
for {
select {
case <-snapshotTicker:
takeSnapshot()
case <-saveAfter:
saveSnapshot()
return
default:
waitForData()
time.Sleep(500 * time.Millisecond)
}
}
}
// TEST SUITE - Don't touch below this line
func takeSnapshot() {
fmt.Println("Taking a backup snapshot...")
}
func saveSnapshot() {
fmt.Println("All backups saved!")
}
func waitForData() {
fmt.Println("Nothing to do, waiting...")
}
func test() {
snapshotTicker := time.Tick(800 * time.Millisecond)
saveAfter := time.After(2800 * time.Millisecond)
saveBackups(snapshotTicker, saveAfter)
fmt.Println("===========================")
}
func main() {
test()
}

View File

@@ -0,0 +1,11 @@
Nothing to do, waiting...
Nothing to do, waiting...
Taking a backup snapshot...
Nothing to do, waiting...
Nothing to do, waiting...
Taking a backup snapshot...
Nothing to do, waiting...
Taking a backup snapshot...
Nothing to do, waiting...
All backups saved!
===========================

View File

@@ -0,0 +1,58 @@
# Select Default Case
The `default` case in a `select` statement executes *immediately* if no other channel has a value ready. A `default` case stops the `select` statement from blocking.
```go
select {
case v := <-ch:
// use v
default:
// receiving from ch would block
// so do something else
}
```
## Tickers
* [time.Tick()](https://golang.org/pkg/time/#Tick) is a standard library function that returns a channel that sends a value on a given interval.
* [time.After()](https://golang.org/pkg/time/#After) sends a value once after the duration has passed.
* [time.Sleep()](https://golang.org/pkg/time/#Sleep) blocks the current goroutine for the specified amount of time.
## Read-only Channels
A channel can be marked as read-only by casting it from a `chan` to a `<-chan` type. For example:
```go
func main(){
ch := make(chan int)
readCh(ch)
}
func readCh(ch <-chan int) {
// ch can only be read from
// in this function
}
```
## Write-only Channels
The same goes for write-only channels, but the arrow's position moves.
```go
func writeCh(ch chan<- int) {
// ch can only be written to
// in this function
}
```
## Assignment
Like all good back-end engineers, we frequently save backup snapshots of the Mailio database.
Complete the `saveBackups` function.
It should read values from the `snapshotTicker` and `saveAfter` channels simultaneously.
* If a value is received from `snapshotTicker`, call `takeSnapshot()`
* If a value is received from `saveAfter`, call `saveSnapshot()` and `return` from the function: you're done.
* If neither channel has a value ready, call `waitForData()` and then [time.Sleep()](https://pkg.go.dev/time#example-Sleeps) for 500 milliseconds. After all, we want to show in the logs that the snapshot service is running.

View File

@@ -0,0 +1,8 @@
{
"question": "What happens when you read from a nil channel?",
"answers": [
"The receiver will block forever",
"The sender will block forever",
"Panic"
]
}

View File

@@ -0,0 +1,33 @@
# Channels Review
Here are a few extra things you should understand about channels from [Dave Cheney's awesome article](https://dave.cheney.net/2014/03/19/channel-axioms).
## A send to a nil channel blocks forever
```go
var c chan string // c is nil
c <- "let's get started" // blocks
```
## A receive from a nil channel blocks forever
```go
var c chan string // c is nil
fmt.Println(<-c) // blocks
```
## A send to a closed channel panics
```go
var c = make(chan int, 100)
close(c)
c <- 1 // panic: send on closed channel
```
## A receive from a closed channel returns the zero value immediately
```go
var c = make(chan int, 100)
close(c)
fmt.Println(<-c) // 0
```

View File

@@ -0,0 +1,8 @@
{
"question": "What happens when you send to a closed channel?",
"answers": [
"Panic",
"The sender will block forever",
"The receiver will block forever"
]
}

View File

@@ -0,0 +1,33 @@
# Channels Review
Here are a few extra things you should understand about channels from [Dave Cheney's awesome article](https://dave.cheney.net/2014/03/19/channel-axioms).
## A send to a nil channel blocks forever
```go
var c chan string // c is nil
c <- "let's get started" // blocks
```
## A receive from a nil channel blocks forever
```go
var c chan string // c is nil
fmt.Println(<-c) // blocks
```
## A send to a closed channel panics
```go
var c = make(chan int, 100)
close(c)
c <- 1 // panic: send on closed channel
```
## A receive from a closed channel returns the zero value immediately
```go
var c = make(chan int, 100)
close(c)
fmt.Println(<-c) // 0
```