Print prime numbers using worker pool:
package main
import (
"fmt"
"sync"
"time"
)
type resset struct {
jobId, res int
}
func main() {
starttime := [Link]()
printPrime()
[Link]([Link](starttime))
}
func printPrime() {
maxGoRoutine := 4
maxNumber := 900
jobCh := make(chan int, maxNumber)
resChan := make(chan resset, maxNumber)
var wg [Link]
for i := 0; i < maxGoRoutine; i++ {
[Link](1)
go worker(jobCh, resChan, &wg, i)
}
for i := 2; i < maxNumber; i++ {
jobCh <- i
}
close(jobCh)
[Link]()
close(resChan)
for prime := range resChan {
[Link]([Link], ":", [Link])
}
}
func worker(job <-chan int, res chan<- resset, wg *[Link], workerId int) {
defer [Link]()
for num := range job {
if isPrime(num) {
res <- resset{jobId: workerId, res: num}
}
}
}
func isPrime(num int) bool {
for i := 2; i*i <= num; i++ {
if num%i == 0 {
return false
}
}
return true
}
Problem 5 linkedIn Concurrency Go
Write a Bounded Cache
Write a bounded (limited number of entries) cache.
Your task: Implement the missing methods and fields for Cache. You must revoke an
entry if it's older than ttl.
Parameters
size: integer - maximal number of entries in the cache. tt]: Maximal time a cache
entry can live.
Want a hint?
Learn about Go arrays and slices in this course on Linkedin
Learning.
Question:
package main
import (
"time"
)
type Cache struct {
// TODO
}
func New(size int, ttl [Link]) (*Cache, error) {
return nil, nil
}
func (c *Cache) Close() {
}
func (c *Cache) Get(key string) (any, bool) {
return nil, false
}
func (c *Cache) Set(key string, value any) {
}
func (c *Cache) Keys() []string {
return nil
}
Answer:
package main
import (
"fmt"
"sync"
"time"
)
type entry struct {
val any
time [Link]
}
type Cache struct {
size int
ttl [Link]
mu [Link] //Multiple read one write
m map[string]entry
}
func New(size int, ttl [Link]) (*Cache, error) {
if size <= 0 {
return nil, [Link]("invalid size - %d", size)
}
if ttl <= 0 {
return nil, [Link]("invalid ttl - %v", ttl)
}
c := Cache{
size: size,
ttl: ttl,
m: make(map[string]entry),
}
return &c, nil
}
func (c *Cache) Close() {
}
func (c *Cache) Get(key string) (any, bool) {
[Link]()
defer [Link]()
e, ok := c.m[key]
if !ok {
return nil, false
}
return [Link], true
}
func (c *Cache) Set(key string, value any) {
[Link]()
defer [Link]()
if len(c.m) == [Link] {
[Link]()
}
e := entry{value, [Link]()}
c.m[key] = e
}
func (c *Cache) popOne() {
minKey, minTime := "", [Link]()
for k, e := range c.m {
if [Link](minTime) {
minKey, minTime = k, [Link]
}
}
delete(c.m, minKey)
}
func (c *Cache) Keys() []string {
[Link]()
defer [Link]()
keys := make([]string, len(c.m))
for k := range c.m {
keys = append(keys, k)
}
return keys
}
//everything is working now to delete entries which are older than that amount
time.
package main
import (
"context"
"fmt"
"sync"
"time"
)
type entry struct {
val any
time [Link]
}
type Cache struct {
size int
ttl [Link]
mu [Link] //Multiple read one write
m map[string]entry
}
func New(size int, ttl [Link]) (*Cache, error) {
if size <= 0 {
return nil, [Link]("invalid size - %d", size)
}
if ttl <= 0 {
return nil, [Link]("invalid ttl - %v", ttl)
}
c := Cache{
size: size,
ttl: ttl,
m: make(map[string]entry),
}
return &c, nil
}
func (c *Cache) Close() {
}
func (c *Cache) Get(key string) (any, bool) {
[Link]()
defer [Link]()
e, ok := c.m[key]
if !ok {
return nil, false
}
return [Link], true
}
func (c *Cache) Set(key string, value any) {
[Link]()
defer [Link]()
if len(c.m) == [Link] {
[Link]()
}
e := entry{value, [Link]()}
c.m[key] = e
}
func (c *Cache) popOne() {
minKey, minTime := "", [Link]()
for k, e := range c.m {
if [Link](minTime) {
minKey, minTime = k, [Link]
}
}
delete(c.m, minKey)
}
func (c *Cache) Keys() []string {
[Link]()
defer [Link]()
keys := make([]string, len(c.m))
for k := range c.m {
keys = append(keys, k)
}
return keys
}
//cleanup
func (c *Cache)Jaintor(ctx [Link]){
t:=[Link]([Link])
defer [Link]()
for {
select{
case<-t.C:
[Link]()
case<-[Link]():
return
}
}
}
func (c *Cache)cleanup(){
[Link]()
defer [Link]()
now:=[Link]()
for key,val:=range c.m{
if [Link]([Link])>[Link]{
delete(c.m,key)
}
}
}
//now cleanup is happening Jaintor will call cleanup
now the question is about context: create context and start janitor:
type Cache struct {
size int
ttl [Link]
mu [Link] //Multiple read one write
m map[string]entry
cancle [Link]
}
func New(size int, ttl [Link]) (*Cache, error) {
if size <= 0 {
return nil, [Link]("invalid size - %d", size)
}
if ttl <= 0 {
return nil, [Link]("invalid ttl - %v", ttl)
}
ctx,cancle:=[Link]([Link]())
c := Cache{
size: size,
ttl: ttl,
m: make(map[string]entry),
cancle: cancle,
}
go [Link](ctx)
return &c, nil
}
func (c *Cache) Close() {
[Link]()//notifing Jaintoer everyting is done
}
Full code:
package main
import (
"context"
"fmt"
"sync"
"time"
)
type entry struct {
val any
time [Link]
}
type Cache struct {
size int
ttl [Link]
mu [Link] //Multiple read one write
m map[string]entry
cancle [Link]
}
func New(size int, ttl [Link]) (*Cache, error) {
if size <= 0 {
return nil, [Link]("invalid size - %d", size)
}
if ttl <= 0 {
return nil, [Link]("invalid ttl - %v", ttl)
}
ctx,cancle:=[Link]([Link]())
c := Cache{
size: size,
ttl: ttl,
m: make(map[string]entry),
cancle: cancle,
}
go [Link](ctx)
return &c, nil
}
func (c *Cache) Close() {
[Link]()//notifing Jaintoer everyting is done
}
func (c *Cache) Get(key string) (any, bool) {
[Link]()
defer [Link]()
e, ok := c.m[key]
if !ok {
return nil, false
}
return [Link], true
}
func (c *Cache) Set(key string, value any) {
[Link]()
defer [Link]()
if len(c.m) == [Link] {
[Link]()
}
e := entry{value, [Link]()}
c.m[key] = e
}
func (c *Cache) popOne() {
minKey, minTime := "", [Link]()
for k, e := range c.m {
if [Link](minTime) {
minKey, minTime = k, [Link]
}
}
delete(c.m, minKey)
}
func (c *Cache) Keys() []string {
[Link]()
defer [Link]()
keys := make([]string,0, len(c.m))
for k := range c.m {
keys = append(keys, k)
}
return keys
}
//cleanup
func (c *Cache)Jaintor(ctx [Link]){
t:=[Link]([Link])
defer [Link]()
for {
select{
case<-t.C:
[Link]()
case<-[Link]():
return
}
}
}
func (c *Cache)cleanup(){
[Link]()
defer [Link]()
now:=[Link]()
for key,val:=range c.m{
if [Link]([Link])>[Link]{
delete(c.m,key)
}
}
}
Console output
2024/09/01 16:10:43 info: creating cache: size=5, ttl=10ms
2024/09/01 16:10:43 info: OK
2024/09/01 16:10:43 info: checking TTL
2024/09/01 16:10:43 info: OK
2024/09/01 16:10:43 info: checking overflow
2024/09/01 16:10:43 info: OK
2024/09/01 16:10:43 info: checking concurrency (15 goroutines, 1000 loops each)
2024/09/01 16:10:43 info: OK
You did it! This result is exactly right.
--- -- -- -- -- -- -- -- -- -- -- --
Test code:
keyFmt := "key-%02d"
keyName := func(i int) string { return [Link](keyFmt, i) }
size := 5
ttl := 10 * [Link]
[Link]("info: creating cache: size=%d, ttl=%v", size, ttl)
c, err := New(size, ttl)
if err != nil {
[Link]("error: can't create - %s", err)
return
}
[Link]("info: OK")
[Link]("info: checking TTL")
key, val := keyName(1), 3
[Link](key, val)
v, ok := [Link](key)
if !ok || v != val {
[Link]("error: %q: got %v (ok=%v)", key, v, ok)
return
}
// Let key expire
[Link](2 * ttl)
_, ok = [Link](key)
if ok {
[Link]("error: %q: got value after TTL", key)
return
}
[Link]("info: OK")
[Link]("info: checking overflow")
n := size * 2
for i := 0; i < n; i++ {
[Link](keyName(i), i)
}
_, ok = [Link](keyName(1))
if ok {
[Link]("error: %q: got value after overflow", key)
return
}
_, ok = [Link](keyName(n - 1))
if !ok {
[Link]("error: %q: not found", key)
return
}
[Link]("info: OK")
numGr := size * 3
count := 1000
[Link]("info: checking concurrency (%d goroutines, %d loops each)",
numGr, count)
var wg [Link]
[Link](numGr)
for i := 0; i < numGr; i++ {
key := keyName(i)
go func() {
defer [Link]()
for i := 0; i < count; i++ {
[Link]([Link])
[Link](key, i)
}
}()
}
[Link]()
[Link]("info: OK")
Solution: Bounded cache
Selecting transcript lines in this section will navigate to timestamp in the video
- [Instructor] So we need to implement this cache. Let's start thinking about what
we need in this cache. So we need to know the size, which is an integer, and we
need to have some kind of a map to know what's going on. We also need to keep the
ttl here, and now we need and, which is a map, and the keys are strings. And
ideally we should save the values, which are any or the empty interface. And if
you're using generics you can probably make it more type safe. But I wanted to make
this exercise simple. So we need to keep an eye also on the time to leave when an
entry was entered to the cache. So I'm going to define something which is an entry,
which is struct, which is going to have the value, which is any, and also the time
that it was entered into this cache. Right, so now we have this time and because we
need to be growing safe, we need a sync. Mutex. And I'm going to reuse a read write
mutex. So I allow multiple readers and a single writer. Okay, so first thing is the
new function. And we should always validate the parameters. Right, so if size is
less or equal to zero, we return nil and we going to say invalid size and then the
size and also the time to leave or the ttl. So if ttl is also zero, return nil and
[Link] invalid ttl. And this time we're going to use the percent v, because
this is a [Link], not the number. Okay, so now let's start with c is a cache
which has the size, which is the size, the ttl, which is the ttl. And we need to
make a map in order to use it, which is mapped from string to any. Not any, sorry,
to entry, my bad. Okay, so now we have the cache and we return a pointer to the
cache and we talk about close in a bit. I'm not going to implement it now. Let's
start with get. We have the mutex and now this is a reader lock and we defer the
[Link] like this. And then we say e and ok equal cache .map and we get the key.
And if not, okay, we return nil and false, saying we couldn't find this entry in
the map. Otherwise we return the [Link] and through signal find that we found
it in the cache and the set is going to be interesting. So now we are going to do
the lock, which is a write lock now because we're going to change it and we defer
[Link], so it's not locked. And now if len of the map equal to the size, this
means that we need to remove an entry. So I'm going to call popOne and we are going
to write it in a bit. And now we can create an entry with the value and [Link].
This is the time we entered it to the cache and we set it in the map. So the key
equal e. Okay, so now we need to do a C cache popOne. So how does popOne works
except for syntax errors? We need to find the entry which has the oldest time. So
I'm going to do the mean key and the mean time equal this. And let's say [Link].
Right, we need something which is bigger than all the entries in the map, and this
is the current time. So now for key and entry in the map, if [Link] is before the
minimum time, going to say that the mean key and minT equal to k and e .time, like
this. Okay, so we're just searching for the minimum and finally we delete from c.m
the minimum. Okay? Okay, so now we deleted the entry that has the latest time and
this is makes the cache impounded cache. And we want to get the keys also from the
cache. And this is again going to use the Rlock because this is a reader lock. We
are not changing, we're just collecting them. And we are going to defer
[Link]. And then we're going to say that keys equal make a slice of string
with initial capacity of zero and len of c.m, because we know how many entries are
going to be there. And then for k in range c.m, keys equal append to keys and the
key. And finally return the keys. We have the keys, and this is pretty
straightforward code. So this is almost working. What we are missing now is the
time to leave, meaning we need to delete entries once the time is older than that
amount of time. And this is going to be interesting and there are several
approaches to do it. What I prefer to do is actually create a goal routine that is
going to do exactly that. Okay, so let's add it to the bottom of this code. Okay,
so we have the keys and I usually call these cleanup methods janitors. Right, what
I'm going to do is I'm going to create a new timer with [Link] and then I'm going to
defer [Link]. And now I'm going to do a for loop and I'm going to say that select
t.C, meaning the timer has ticked, sorry, select and then case get the t, meaning
that we have a tick on the timer meaning one ttl has passed. We need to call the
cleanup function and we'll write this one in a second. Right, so I'm going to call
cleanup. The thing is I want to tell the janitor also that, hey, you know, we're
done. Please don't do it anymore. So I'm missing a bracket, no. So this is going to
the select, this is going to the for and this is going to the define, all right.
But we need to tell this janitor to stop sometimes, right? So the best way to do it
is usually pass it the context, which is the [Link]. And now if we are
going just to return. So this is the way we can tell the janitor that this is gone
and don't forget to import context here. All right, so the cleanup code is pretty
straightforward. Right, we need to again, [Link]. and this time it's a writer lock
because we are going to make some changes and we're going to defer [Link]. And
then we are going to say that t is [Link]. And then for key value in range c.m if
now sub [Link] is bigger than the time to leave, we are, sorry, sub not sum, we are
going to delete the entry from the map. Sorry, let's call this one now. Now. Okay,
now we're happy. Okay, so now we have this janitor. This janitor wakes up every ttl
and calls cleanup and the cleanup checks for anything that is out. And then there
is also a way to notify that we're done. So the question is, what is this context,
right? We need to create a context and we need to start the janitor and we're going
to do it here when we start one. Okay, so once we created this one, we also need to
create a context. And you know what, I'm going to create the context here. ctx, we
want a context and a cancel function, because we just manually cancel. So we're
going to say context with cancel and [Link], because we start from the
background context, and I'm going to put the cancel here so we can cancel the
janitor later. And now I can do go [Link] with the context. And here we can have
cancel, which is of type [Link] function. Right, so now we have this
janitor. Once we start the cache it is running in the background and deleting all
entries. And when we close, we just call cancel function, notifying that the
janitor that everything is done. And here we have various tests about checking
keys, checking overflow and checking time to live. Right, so let's test my code and
everything seems fine.
Limit Number of goroutines
CenterDir
creates images sequentially.
Your task: Convert CenterDir to run concurrently, but limit the number of
goroutines to
Parameters
ctx: [Link]
sreDir: string - source directory destDir: string - destination directory
n: int - number of goroutines
Output
err: Error
Constraints
* Don't change the Center function.
* Use only n goroutines.
Question:
package main
import (
"context"
"errors"
"fmt"
"image"
"image/draw"
"image/jpeg"
"io/fs"
"os"
"path/filepath"
)
// Center creates destFile which is the center of image encode in data.
func Center(srcFile, destFile string) error {
file, err := [Link](srcFile)
if err != nil {
return err
}
defer [Link]()
src, err := [Link](file)
if err != nil {
return err
}
x, y := [Link]().Max.X, [Link]().Max.Y
r := [Link](0, 0, x/2, y/2)
dest := [Link](r)
[Link](dest, [Link](), src, [Link]{x / 4, y / 4}, [Link])
out, err := [Link](destFile)
if err != nil {
return err
}
defer [Link]()
return [Link](out, dest, nil)
}
// CenterDir calls Center on every image in srcDir. n is the maximal number of
goroutines.
func CenterDir(ctx [Link], srcDir, destDir string, n int) error {
if err := [Link](destDir, 0750); err != nil && 
{
return err
}
matches, err := [Link]([Link]("%s/*.jpg", srcDir))
if err != nil {
return err
}
for _, src := range matches {
dest := [Link]("%s/%s", destDir, [Link](src))
if err := Center(src, dest); err != nil {
return err
}
return nil
}
Answer:
package main
import (
"context"
"errors"
"fmt"
"image"
"image/draw"
"image/jpeg"
"io/fs"
"os"
"path/filepath"
)
// Center creates destFile which is the center of image encode in data.
func Center(srcFile, destFile string) error {
file, err := [Link](srcFile)
if err != nil {
return err
}
defer [Link]()
src, err := [Link](file)
if err != nil {
return err
}
x, y := [Link]().Max.X, [Link]().Max.Y
r := [Link](0, 0, x/2, y/2)
dest := [Link](r)
[Link](dest, [Link](), src, [Link]{x / 4, y / 4}, [Link])
out, err := [Link](destFile)
if err != nil {
return err
}
defer [Link]()
return [Link](out, dest, nil)
}
type request struct{
src string
dest string
}
func worker(ctx [Link], in <-chan request,out chan<- error){
for{
select{
case r,ok:=<-in:
if !ok{
return
}
out<- Center([Link],[Link])
case <-[Link]():
return
}
}
}
func producer(ctx [Link], in chan<- request, srcFile []string, destDir
string){
defer close(in)
for _,src:=range srcFile{
dest:=[Link]("%s/%s",destDir, [Link](src))
select{
case in<-request{src,dest}:
case <-[Link]():
return
}
}
// CenterDir calls Center on every image in srcDir. n is the maximal number of
goroutines.
func CenterDir(ctx [Link], srcDir, destDir string, n int) error {
if err := [Link](destDir, 0750); err != nil && 
{
return err
}
matches, err := [Link]([Link]("%s/*.jpg", srcDir))
if err != nil {
return err
}
in,out:=make(chan request), make(chan error,len(matches))
for i:=0;i<n;i++{
go worker(ctx,in,out)
}
go producer(ctx, in,matches, destDir)
for range matches{
select{
case err:=<-out:
if err!=nil{
return err
}
case <-[Link]():
return [Link]()
}
return nil
}
Console output
2024/09/03 01:20:44 info: finished in 171.717719ms (err=<nil>)
2024/09/03 01:20:44 info: max goroutines - 5
Correct
--- -- -- -- -- -- -- -- -- -- -- --
tests:
start := [Link]()
ctx, cancel := [Link]([Link](), 5 * [Link])
defer cancel()
n := [Link](0) // number of cores
err := CenterDir(ctx, srcDir, destDir, n)
duration := [Link](start)
[Link]("info: finished in %v (err=%v)", duration, err)
Solution: Resizing images
Selecting transcript lines in this section will navigate to timestamp in the video
- [Instructor] So we need to convert CenterDir to run concurrently and to limit the
number of goroutines by end. And there are two approaches to do it. One is that
every time before you launch a goroutine, you check the current number of running
goroutines and then if there are end goroutines already running, you wait until one
of them terminates. The other one is to pre-launch and work your goroutines and
then send jobs to them. And this is what I'm going to do but the other way is also
fine. So I'll start with the request that is going to go for the worker goroutine.
And basically it's going to have the source file and the destination file. And now
I'm going to create my worker and it is going to get a context to know if something
is done. The input channel, which is a channel of request. And we signify that this
is a channel should read from and the output channel, and this is a channel it
should write into off errors. And basically a worker does an infinitely, what it
does is does select on the input channel. So if we get something from the input
channel we need to check. If not, okay, this means that someone closed the input
channel. So we return, otherwise we send to the output channel the result of
calling center with [Link] and [Link]. And we also need to check if the context has
expired and if it does, we return. So now our worker is running, receiving from the
input channel and sending arrows to the output channel. And I'm going to also
create a producer. And this producer is going to send these requests into the input
channel, right? So it also get the context, it'll get the in which is a channel.
And this time the producer is writing to this channel and we get source files,
which is a slice of strings and the destination directory, which is a string. And
we need to make sure that when the producer is done, it's closing the input
channel, telling basically all the worker goroutines, no more input is coming. And
now for every source file in the source files, we formatted destination. So this is
[Link] of %s/%s with the destDir, destination directory and [Link]
(src). So basically it's just going to give me the base name for the file. And now
we do a select again. And this time if we manage to send into the input a request
with src and dest, this is great, oops. Like this, this is great. And basically we
don't need to do anything. So no operation. But if we manage to get something from
the context done, we return. This means nothing is done more. By the way, when we
return, we close the input channel and all the workers will get notified. And now
we can go and change our center there, right? So the first part when we get the
names of the file, this is fine. And now we need to create our in and out channels.
So it's going to be a channel of request and this is going to be a channel of
errors. And because I know how many errors are coming out and I don't want the
goroutines, the workers to block when they're sending to the error channel, I'm
going to use a buffer channel with len of matches. So now we have these two
channels, and now for i equals zero, i smaller than n, i plus plus, I'm going to
spin now a worker with the context that we have the input that matches and the
destination director. The worker just gets the input and the output channels and
that's it, right? Look at the worker input and output channel. So again, this is
sending the work. So now the worker is ready, sorry, we didn't send the work
anymore but now we can call the producer with the contacts, the input channel, the
matches and the destination directory. And now we have another goroutine which is
the producer that is running. Now we just need to collect the results. So we can do
for range of matches which is going to replace this one. And basically what we are
going to do again is select, case, err, we got something from the output channel.
And if it's not nil, we are going to return it. And if the context expires, we are
going to return and the context has an error. So this is the error that we're going
to return. So this is the for. And finally we're going to return nil, meaning
everything went well. Let's test the code. And seems like it works.
Limit Runtime of Movie Recommendation
BestNextMovie
recommends the best next movie for a login. It takes some time
to run and we'd like to limit this time.
Your task: Implement NextMovie
that should return the result of BestNextMovie
if it finished before ct expired, otherwise it should return defaultMovie.
Parameters
ctx
user
context. Context - the amount of time we have string - the user to pass to
BestNextMovie
Output
A
Movie.
Constraints
* Don't change
BestNextMovie
Question:
package main
import (
"context"
"time"
)
var (
// Everybody loves "The Princess Bride"
defaultMovie = Movie{
ID: "tt0093779",
Title: "The Princess Bride",
}
// Time it takes for BestNextMovie to finish
bmvTime = 50 * [Link]
)
// Movie is a movie recommendation
type Movie struct {
ID string
Title string
}
// BestNextMovie return the best move recommendation for a user
func BestNextMovie(user string) Movie {
[Link](bmvTime) // Simulate work
// Don't change this, otherwise the test will fail
return Movie{
ID: "tt0083658",
Title: "Blade Runner",
}
}
// NextMovie return BestNextMovie result if it finished before ctx expires,
otherwise defaultMovie
func NextMovie(ctx [Link], user string) Movie {
// FIXME: You code goes here
return Movie{}
}
Answer:
package main
import (
"context"
"time"
)
var (
// Everybody loves "The Princess Bride"
defaultMovie = Movie{
ID: "tt0093779",
Title: "The Princess Bride",
}
// Time it takes for BestNextMovie to finish
bmvTime = 50 * [Link]
)
// Movie is a movie recommendation
type Movie struct {
ID string
Title string
}
// BestNextMovie return the best move recommendation for a user
func BestNextMovie(user string) Movie {
[Link](bmvTime) // Simulate work
// Don't change this, otherwise the test will fail
return Movie{
ID: "tt0083658",
Title: "Blade Runner",
}
}
// NextMovie return BestNextMovie result if it finished before ctx expires,
otherwise defaultMovie
func NextMovie(ctx [Link], user string) Movie {
// FIXME: You code goes here
/*
Why buffered channal? Think you have after sleep duration the movie is
returned, whic is then writtend to channal,
as we have encounterd context timeout,
ch<-m
movie written to the channal, goroutine is active for someone to receive from
channal.
this lead to garbage collection.
so make it buffred which will close once goroutine written to channal
ch:=make(chan Movie)
*/
ch:=make(chan Movie,1)
go func(){
m:=BestNextMovie(user)
ch<-m
}()
select{
case m:=<-ch:
return m
case <-[Link]():
return defaultMovie
}
}
Console output
2024/09/03 01:22:42 info: checking finish in time
2024/09/03 01:22:42 info: got {ID:tt0083658 Title:Blade Runner}
2024/09/03 01:22:42 info: checking timeout
2024/09/03 01:22:42 info: got {ID:tt0093779 Title:The Princess Bride}
That's right!
--- -- -- -- -- -- -- -- -- -- -- --
[Link]("info: checking finish in time")
ctx, cancel := [Link]([Link](), bmvTime*2)
defer cancel()
mOK := NextMovie(ctx, "ridley")
[Link]("info: got %+v", mOK)
[Link]("info: checking timeout")
ctx, cancel = [Link]([Link](), bmvTime/2)
defer cancel()
mTimeout := NextMovie(ctx, "ridley")
[Link]("info: got %+v", mTimeout)
Solution: Movie recommendation
Selecting transcript lines in this section will navigate to timestamp in the video
- [Instructor] We have the defaultMovie. This is the movie that we should return if
there is a timeout and we have the bmvTime, which is the time it takes for the
BestNextMovie to run. And we have this Movie struct that we are going to return.
And the BestNextMovie is the one that we have. We can't change it. It simulates
work by sleeping the bmvTime and then returns "Blade Runner" as a Movie. The
NextMovie should return either BestNextMovie if context that we get here does not
expire, otherwise it should return the defaultMovie which is "The Princess Bride,"
and everybody loves "The Princess Bride." In the test code, we have two scenarios.
One, we have bmvTime times two means we have enough time, so we should see "Blade
Runner" printed out, and the second time we give it half the time. So this should
timeout and you should get "The Princess Bride." So let's start. When we talk about
timeouts and cancellation, we work with context and if you want to run code, we
need to work with go routines and the select statement. So I'm going to start with
making a channel of the movies and this is the channel that the go routine is going
to return. And now I'm going to run a function that will get the movie from the
BestNextMovie with the user. And then we'll send this over the channel. Alright, so
now the BestNextMovie is running concurrently. And now we can get back what we want
and we can listen on two things. So we're going to do select and the first one if
I'm getting something from the channel, it means that BestNextMovie has finished in
time and I can just return what we got. On the other case, if the context .done has
fired then I'm not really interested in the results. So just the fact that
something is available on the channel, we are going to return the defaultMovie.
Okay, so really easy to implement timeouts and this looks fine. There is just one
problem with this code. If you're going to run it, actually let's test it first and
it's good. But there is an issue here. In the case of a timeout, what is going to
happen that this routine is going to finish after we exited next movie. And now
it's going to try and write to the channel and nobody's reading from the channel.
So we have a stuck go routine and a channel that it's pointing to that's not going
to be cleared by the garbage collector. And this is known as a go routine leak. The
best way to solve it is to make this channel a buffered channel with a size of one.
Now the first sign is not going to block, so the go routine is going to exit. The
channel is going to be cleared by the garbage collector and refine. And if you run
test my code again we'll see that it works as well.
Validate Digital Signatures Concurrently
The current implementation of validatesigs works sequentialy.
Your task: Convert Validatesigs
to work concurrently.
Constraints
* Do not change the implementation of shalsig.
Want a hint?
Learn about Go concurrency in this course on LinkedIn Learning.
Question:
package main
import (
"bytes"
"crypto/sha1"
"fmt"
"io"
)
// sha1sig return SHA1 signature in the format
"35aabcd5a32e01d18a5ef688111624f3c547e13b"
func sha1Sig(data []byte) (string, error) {
w := [Link]()
r := [Link](data)
if _, err := [Link](w, r); err != nil {
return "", err
}
sig := [Link]("%x", [Link](nil))
return sig, nil
}
type File struct {
Name string
Content []byte
Signature string
}
// ValidateSigs return slice of OK files and slice of mismatched files
func ValidateSigs(files []File) ([]string, []string, error) {
var okFiles []string
var badFiles []string
for _, file := range files {
sig, err := sha1Sig([Link])
if sig != [Link] || err != nil {
badFiles = append(badFiles, [Link])
} else {
okFiles = append(okFiles, [Link])
}
}
return okFiles, badFiles, nil
}
Answer:
package main
import (
"bytes"
"crypto/sha1"
"fmt"
"io"
)
// sha1sig return SHA1 signature in the format
"35aabcd5a32e01d18a5ef688111624f3c547e13b"
func sha1Sig(data []byte) (string, error) {
w := [Link]()
r := [Link](data)
if _, err := [Link](w, r); err != nil {
return "", err
}
sig := [Link]("%x", [Link](nil))
return sig, nil
}
type File struct {
Name string
Content []byte
Signature string
}
type reply struct{
fileName string
match bool
err error
}
func sigWorker(file File, ch chan<- reply){
sig, err := sha1Sig([Link])
r:=reply{
fileName: [Link],
match: [Link]==sig,
err: err,
}
ch<-r
}
// ValidateSigs return slice of OK files and slice of mismatched files
func ValidateSigs(files []File) ([]string, []string, error) {
var okFiles []string
var badFiles []string
ch:=make(chan reply)
//fan out
for _, file := range files {
go sigWorker(file, ch)
}
//collect results
for range files{
r:=<-ch
if ![Link] || [Link] != nil {
badFiles = append(badFiles, [Link])
} else {
okFiles = append(okFiles, [Link])
}
}
return okFiles, badFiles, nil
}
start := [Link]()
ok, bad, err := ValidateSigs(files)
duration := [Link](start)
[Link]("info: %d files in %v\n", len(ok)+len(bad), duration)
[Link]("ok: %v", ok)
[Link]("bad: %v", bad)
Console output
2024/09/03 01:25:42 info: 20 files in 98.775096ms
2024/09/03 01:25:42 ok: [[Link] [Link] [Link] [Link] [Link]
[Link] [Link] [Link] [Link] [Link] [Link] [Link] srv-
[Link] [Link] [Link] [Link] [Link] [Link]]
2024/09/03 01:25:42 bad: [[Link] [Link]]
2024/09/03 01:25:42 info: 19 goroutines
Another one solved!
--- -- -- -- -- -- -- -- -- -- -- --
Solution: Digital signature check
Selecting transcript lines in this section will navigate to timestamp in the video
- [Instructor] What we need to do is we need to convert this ValidateSigs which is
a sequential code to a concurrent one. And this time we need a return value from
the goroutines. We're not allowed to touch this function, the sha1Sig function. So
we start with what the goroutine should return and it returns it over a channel. So
I'm going to say the type reply is struct and now we need to give the context. So
we need the FileName, which is the string. I'm going to do a Boolean whether there
was a match or not. And I'm going to say also if there was an error. So this is
what the goroutine returns and now when we get it over a channel, we know exactly
how to deal. Now I'm going to write my sigWorker. And the sigWorker is the
goroutine that is going to happen. You can do it in an anonymous function inside
the main loop, but I decided to do it here. So we going to pass it a file and a
channel to return the values and we say that this is a channel that it should send
to and then replies. And now we call the sha1Sig on the [Link] and now we
construct the reply. So we have a reply where the fileName is the [Link]. The
match is whether the signature that we got equal the [Link] and err is the
error that we got. And finally we send it back on the channel, right? So now we
have this worker, it's pretty simple code and now we can work out. Now we need to
split this code into two. One is the fan out part, so we're going to do fan out.
And what we're going to do is first we need to create a channel. Okay, so make chan
of replies and now we launch a goroutine with the sigWorker with the file and the
channel. Next we need to collect the results. Okay, so I need to get end results
where n is the length of the file. So I'm going to use for range in files which
does exactly that. Now here, instead of getting the signature and the error, I'm
receiving from the channel. Okay, so now I have that and now I can do if not
[Link] or and we can delete this. [Link] does not equal nil. We get the file name
and now we have it in [Link]. Otherwise, and again this is going to be in
[Link]. Okay, so fan out collecting results from the channel. And finally
return what we want. Let's test the code and it looks like we're good and we
launched 18 goroutines to do that. You're going to see two bad files and this is
intentional.
Timing HTTP Calls
Currently the
MultiURLTime
sequentially.
function times the calls to a slices or URL
Your task: Convert MultiURLTime
to work concurrently using a goroutine per
URL.
Constraints
* You can call only URLs in the format [Link] 8080/{n} where n is the
delay in milliseconds until the request returns.
* Don't call URLs with very large n
there's a global timeout enforced by
coderpad.
* Do not change the ULTime function.
Question:
// Write your answer here, and then test your code.
// Your job is to convert MultiURLTime to run concurrently.
package main
import (
"io"
"log"
"net/http"
"time"
)
// MutliURLTimes calls URLTime for every URL in URLs.
func MultiURLTime(urls []string) {
for _, url := range urls {
URLTime(url)
}
}
// URLTime checks how much time it takes url to respond.
func URLTime(url string) {
start := [Link]()
resp, err := [Link](url)
if err != nil {
[Link]("error: %q - %s", url, err)
return
}
if [Link] != [Link] {
[Link]("error: %q - bad status - %s", url, [Link])
return
}
// Read body
_, err = [Link]([Link], [Link])
if err != nil {
[Link]("error: %q - %s", url, err)
return
}
duration := [Link](start)
[Link]("info: %q - %v", url, duration)
}
Answer:
// Write your answer here, and then test your code.
// Your job is to convert MultiURLTime to run concurrently.
package main
import (
"io"
"log"
"net/http"
"sync"
"time"
)
var wg [Link]
// MutliURLTimes calls URLTime for every URL in URLs.
func MultiURLTime(urls []string) {
var wg [Link]
for _, url := range urls {
url:=url
[Link](1)
go func(){
defer [Link]()
URLTime(url)
}()
}
[Link]()
}
// URLTime checks how much time it takes url to respond.
func URLTime(url string) {
start := [Link]()
resp, err := [Link](url)
if err != nil {
[Link]("error: %q - %s", url, err)
return
}
if [Link] != [Link] {
[Link]("error: %q - bad status - %s", url, [Link])
return
}
// Read body
_, err = [Link]([Link], [Link])
if err != nil {
[Link]("error: %q - %s", url, err)
return
}
duration := [Link](start)
[Link]("info: %q - %v", url, duration)
}
Test code:
start := [Link]()
urls := []string{
"[Link]
"[Link]
"[Link]
}
MultiURLTime(urls)
duration := [Link](start)
[Link]("%d URLs in %v", len(urls), duration)
Console output
2024/09/03 01:29:53 info: "[Link] - 50.907225ms
2024/09/03 01:29:53 info: "[Link] - 101.370253ms
2024/09/03 01:29:53 info: "[Link] - 201.171362ms
2024/09/03 01:29:53 3 URLs in 201.341934ms
Bravo! This is the correct answer.
Solution: Timing HTTP calls
Selecting transcript lines in this section will navigate to timestamp in the video
- [Instructor] So, we are going to make this MultiURLTime run concurrently, and,
ideally, I'd like just to do go URLTime, and then everything runs in a goroutine.
The problem is that the Go runtime does not wait for goroutines. We need to wait
for them. Because we don't actually need any answers from the goroutine, the thing
that we want to use here is a WaitGroup. So, var wg [Link]. And, we need to
initialize the WaitGroup with how many jobs there are. And this is the len of the
urls. So [Link] (len(urls)). Okay, so now we have a sync. And then at the end what
we need to do, oops, after we do the follow-up, is we need to do [Link] but we
need to notify when a task is done. And this is where we need to actually write our
own function, which is in our case, an anonymous function to do that, okay? But if
you're going to use that, this URL is going to be repeated for all of the
goroutines, the closer capture. So I'm going to create a local variable for the
follow-up. And this way the goroutine will accept it's own URL. They're not going
to run the same URL everyone. And finally defer [Link] signaling that there are no
more URLs. Okay, so let's run. Test my code. Oh, we need to import the sync
package. So we import the sync package and now we can run, Test my code. And now it
works. And we see that even though we have 200, 100 and 53, the total time is 203,
roughly the last one or the longest one.
//What is the output
/*
package main
import (
"fmt"
)
func main() {
s := make([]int, 0, 2)
doSomething(s)
[Link](s)
}
func doSomething(a []int) {
a = append(a, 1)
}
*/
//What is the output
/*
package main
import (
"fmt"
)
func main() {
s := make([]int, 0, 2)
doSomething(s)
[Link](s[:1]) // <-- here I sliced the slice from 1st to 2nd element
}
func doSomething(a []int) {
a = append(a, 1)
}
*/
//Question: What is the time complexity of len(...) for each data type ?
// Answer is O(1)
//What is the outut??
/*
package main
import "fmt"
type gopher struct {
name string
}
func (r *gopher) print() {
[Link]("gopher-printer works!")
}
func main() {
var gpr *gopher
[Link]()
}
*/
//What is the outut??
/*
package main
import "fmt"
type gopher struct {
name string
}
func (r *gopher) print() {
[Link]([Link])
[Link](r)
[Link]("gopher-printer works!")
}
func main() {
var gpr *gopher
[Link]()
}
*/
// What is the output of function
/*
package main
import "fmt"
func main() {
ch := make(chan int, 4)
go func() {
ch <- 1
ch <- 2
ch <- 3
ch <- 4
ch <- 5
close(ch)
}()
for num := range ch {
[Link](num)
}
}
*/
// What is the output of function
/*
package main
import "fmt"
func main() {
ch := make(chan int, 4)
ch <- 1
ch <- 2
ch <- 3
ch <- 4
ch <- 5
close(ch)
for num := range ch {
[Link](num)
}
}
*/
//Question: Which if statements will be evaluated as true?
/*
package main
import "fmt"
type SomeType interface {
Get()
}
type SomeImpl struct {...}
func (i SomeImpl) Get() {...}
func main() {
var aType SomeType
if aType == nil {
[Link]("nil interface")
}
var aImpl *SomeImpl
if aImpl == nil {
[Link]("nil struct")
}
aType = aImpl
if aType == nil {
[Link]("nil assignment")
}
}
*/
//Question: One of your teammates submitted this code for a code review. This code
has a potential threat. Identify it and give a solution to solve it.
/*
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
[Link](2 * [Link])
ch <- 42
[Link]("Sent: 42")
}()
val := <-ch
[Link]("Received:", val)
[Link]("Continuing execution...")
}
*/
/*
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
[Link](2 * [Link])
ch <- 42
[Link]("Sent: 42")
}()
for {
select {
case val := <-ch:
[Link]("Received:", val)
[Link]("Continuing execution...")
return
default:
[Link]("No value received")
[Link](500 * [Link]) // Sleep for a while to
prevent busy looping
// handle the execution flow of instructions and operations that
must continue
}
}
[Link]("Continuing execution...")
}
*/
/*
package main
import (
"context"
"fmt"
"sync"
"time"
)
func main() {
ctx, cancle := [Link]([Link](), 5*[Link])
defer cancle()
var wg [Link]
[Link](2)
go vendor1(ctx, &wg)
go vendor2(ctx, &wg)
[Link]()
}
func vendor1(ctx [Link], wg *[Link]) {
defer [Link]()
select {
case <-[Link]():
[Link]("vendor1: context cancelled")
return
case <-[Link](10 * [Link]):
[Link]("vendor1: completed work")
return
}
}
func vendor2(ctx [Link], wg *[Link]) {
defer [Link]()
select {
case <-[Link]():
[Link]("vendor2: context cancelled")
return
case <-[Link](5 * [Link]):
[Link]("vendor2: completed work")
return
}
}
*/
Given number 1 to n: launch 2 goroutine which only check even and other check odd.
you should not print the values in those goroutine but print simentiously
Example:
0
1
2
3
4
5
6
7
8
9
you can use main function to print or use 3rd goroutine to print.
package main
import (
"fmt"
"sync"
)
func main() {
var wg [Link]
sig := make(chan struct{})
rec := make(chan int, 100)
[Link](2)
go even(sig, rec, &wg)
go odd(sig, rec, &wg)
[Link]()
close(rec)
for i := range rec {
[Link](i)
}
}
func odd(sig chan struct{}, rec chan int, wg *[Link]) {
defer [Link]()
for i := 0; i < 10; i++ {
if i%2 == 1 {
rec <- i
}
sig <- struct{}{}
}
}
func even(sig chan struct{}, rec chan int, wg *[Link]) {
defer [Link]()
for i := 0; i < 10; i++ {
<-sig
if i%2 == 0 {
rec <- i
}
}
}
[Link]
//2 functions :
//func1(sendAndReceive) will send 1 to 100 to filterEven
//func2(filterEven) will filter even numbers and send those even numbers back to
sendAndReceive function
// both should be called using goroutine and communication should be with channel
Square of 100 number 100 task lunch 10 goroutines
2 goroutine vendor1: sleeps for 10 second vendor2: sleeps for 20 second execute
this cancel if it exceeds 10 seconds