diff --git a/lectures/03-goroutines/chat/chat.go b/lectures/03-goroutines/chat/chat.go new file mode 100644 index 0000000..914eab1 --- /dev/null +++ b/lectures/03-goroutines/chat/chat.go @@ -0,0 +1,93 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 254. +//!+ + +// Chat is a server that lets clients chat with each other. +package main + +import ( + "bufio" + "fmt" + "log" + "net" +) + +//!+broadcaster +type client chan<- string // an outgoing message channel + +var ( + entering = make(chan client) + leaving = make(chan client) + messages = make(chan string) // all incoming client messages +) + +func broadcaster() { + clients := make(map[client]bool) // all connected clients + for { + select { + case msg := <-messages: + for cli := range clients { + cli <- msg + } + case cli := <-entering: + clients[cli] = true + case cli := <-leaving: + delete(clients, cli) + close(cli) + } + } +} + +//!-broadcaster + +//!+handleConn +func handleConn(conn net.Conn) { + ch := make(chan string) // outgoing client messages + go clientWriter(conn, ch) + + who := conn.RemoteAddr().String() + ch <- "You are " + who + messages <- who + " has arrived" + entering <- ch + + input := bufio.NewScanner(conn) + for input.Scan() { + messages <- who + ": " + input.Text() + } + // NOTE: ignoring potential errors from input.Err() + leaving <- ch + messages <- who + " has left" + conn.Close() +} + +func clientWriter(conn net.Conn, ch <-chan string) { + for msg := range ch { + fmt.Fprintln(conn, msg) // NOTE: ignoring network errors + } +} + +// END_WRITER OMIT + +//!-handleConn + +//!+main +func main() { + listener, err := net.Listen("tcp", "localhost:8000") + if err != nil { + log.Fatal(err) + } + + go broadcaster() + for { + conn, err := listener.Accept() + if err != nil { + log.Print(err) + continue + } + go handleConn(conn) + } +} + +//!-main diff --git a/lectures/03-goroutines/chat/chat.go.~master~ b/lectures/03-goroutines/chat/chat.go.~master~ new file mode 100644 index 0000000..225af17 --- /dev/null +++ b/lectures/03-goroutines/chat/chat.go.~master~ @@ -0,0 +1,89 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +//!+ + +// Chat is a server that lets clients chat with each other. +package main + +import ( + "bufio" + "fmt" + "log" + "net" +) + +//!+broadcaster +type client chan<- string // an outgoing message channel + +var ( + entering = make(chan client) + leaving = make(chan client) + messages = make(chan string) // all incoming client messages +) + +func broadcaster() { + clients := make(map[client]bool) // all connected clients + for { + select { + case msg := <-messages: + // Broadcast incoming message to all + // clients' outgoing message channels. + for cli := range clients { + cli <- msg + } + case cli := <-entering: + clients[cli] = true + case cli := <-leaving: + delete(clients, cli) + close(cli) + } + } +} + +//!-broadcaster + +//!+handleConn +func handleConn(conn net.Conn) { + ch := make(chan string) // outgoing client messages + go clientWriter(conn, ch) + + who := conn.RemoteAddr().String() + entering <- ch + messages <- who + " has arrived" + input := bufio.NewScanner(conn) + for input.Scan() { + messages <- who + ": " + input.Text() + } + messages <- who + " has left" + leaving <- ch + conn.Close() +} + +func clientWriter(conn net.Conn, ch <-chan string) { + for msg := range ch { + fmt.Fprintln(conn, msg) + } +} + +//!-handleConn + +//!+main +func main() { + listener, err := net.Listen("tcp", "localhost:8000") + if err != nil { + log.Fatal(err) + } + + go broadcaster() + for { + conn, err := listener.Accept() + if err != nil { + log.Print(err) + continue + } + go handleConn(conn) + } +} + +//!-main diff --git a/lectures/03-goroutines/clock1/main.go b/lectures/03-goroutines/clock1/main.go new file mode 100644 index 0000000..d770998 --- /dev/null +++ b/lectures/03-goroutines/clock1/main.go @@ -0,0 +1,35 @@ +// Clock1 is a TCP server that periodically writes the time. +package main + +import ( + "io" + "log" + "net" + "time" +) + +func main() { + listener, err := net.Listen("tcp", "localhost:8000") + if err != nil { + log.Fatal(err) + } + for { + conn, err := listener.Accept() + if err != nil { + log.Print(err) // e.g., connection aborted + continue + } + handleConn(conn) // handle one connection at a time + } +} + +func handleConn(c net.Conn) { + defer c.Close() + for { + _, err := io.WriteString(c, time.Now().Format("15:04:05\n")) + if err != nil { + return // e.g., client disconnected + } + time.Sleep(1 * time.Second) + } +} diff --git a/lectures/03-goroutines/clock2/main.go b/lectures/03-goroutines/clock2/main.go new file mode 100644 index 0000000..82b989c --- /dev/null +++ b/lectures/03-goroutines/clock2/main.go @@ -0,0 +1,35 @@ +// Clock1 is a TCP server that periodically writes the time. +package main + +import ( + "io" + "log" + "net" + "time" +) + +func main() { + listener, err := net.Listen("tcp", "localhost:8000") + if err != nil { + log.Fatal(err) + } + for { + conn, err := listener.Accept() + if err != nil { + log.Print(err) // e.g., connection aborted + continue + } + go handleConn(conn) // HL + } +} + +func handleConn(c net.Conn) { + defer c.Close() + for { + _, err := io.WriteString(c, time.Now().Format("15:04:05\n")) + if err != nil { + return // e.g., client disconnected + } + time.Sleep(1 * time.Second) + } +} diff --git a/lectures/03-goroutines/du1/main.go b/lectures/03-goroutines/du1/main.go new file mode 100644 index 0000000..5bed1ab --- /dev/null +++ b/lectures/03-goroutines/du1/main.go @@ -0,0 +1,69 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 247. + +//!+main + +// The du1 command computes the disk usage of the files in a directory. +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +func main() { + flag.Parse() + roots := flag.Args() + if len(roots) == 0 { + roots = []string{"."} + } + + fileSizes := make(chan int64) + go func() { + for _, root := range roots { + walkDir(root, fileSizes) + } + close(fileSizes) + }() + + var nfiles, nbytes int64 + for size := range fileSizes { + nfiles++ + nbytes += size + } + printDiskUsage(nfiles, nbytes) +} + +func printDiskUsage(nfiles, nbytes int64) { + fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9) +} + +// walkDir recursively walks the file tree rooted at dir +// and sends the size of each found file on fileSizes. +func walkDir(dir string, fileSizes chan<- int64) { + for _, entry := range dirents(dir) { + if entry.IsDir() { + subdir := filepath.Join(dir, entry.Name()) + walkDir(subdir, fileSizes) + } else { + fileSizes <- entry.Size() + } + } +} + +// dirents returns the entries of directory dir. +func dirents(dir string) []os.FileInfo { + entries, err := ioutil.ReadDir(dir) + if err != nil { + fmt.Fprintf(os.Stderr, "du1: %v\n", err) + return nil + } + return entries +} + +//-walkDir OMIT diff --git a/lectures/03-goroutines/du2/main.go b/lectures/03-goroutines/du2/main.go new file mode 100644 index 0000000..bb1e9cd --- /dev/null +++ b/lectures/03-goroutines/du2/main.go @@ -0,0 +1,91 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 249. + +// The du2 command computes the disk usage of the files in a directory. +package main + +// The du2 variant uses select and a time.Ticker +// to print the totals periodically if -v is set. + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" +) + +//!+ +var verbose = flag.Bool("v", false, "show verbose progress messages") + +func main() { + // ...start background goroutine... + + flag.Parse() + roots := flag.Args() + if len(roots) == 0 { + roots = []string{"."} + } + + // Traverse the file tree. + fileSizes := make(chan int64) + go func() { + for _, root := range roots { + walkDir(root, fileSizes) + } + close(fileSizes) + }() + + // Print the results periodically. + var tick <-chan time.Time + if *verbose { + tick = time.Tick(500 * time.Millisecond) + } + var nfiles, nbytes int64 +loop: + for { + select { + case size, ok := <-fileSizes: + if !ok { + break loop // fileSizes was closed + } + nfiles++ + nbytes += size + case <-tick: + printDiskUsage(nfiles, nbytes) + } + } + printDiskUsage(nfiles, nbytes) // final totals +} + +//!- + +func printDiskUsage(nfiles, nbytes int64) { + fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9) +} + +// walkDir recursively walks the file tree rooted at dir +// and sends the size of each found file on fileSizes. +func walkDir(dir string, fileSizes chan<- int64) { + for _, entry := range dirents(dir) { + if entry.IsDir() { + subdir := filepath.Join(dir, entry.Name()) + walkDir(subdir, fileSizes) + } else { + fileSizes <- entry.Size() + } + } +} + +// dirents returns the entries of directory dir. +func dirents(dir string) []os.FileInfo { + entries, err := ioutil.ReadDir(dir) + if err != nil { + fmt.Fprintf(os.Stderr, "du: %v\n", err) + return nil + } + return entries +} diff --git a/lectures/03-goroutines/du3/main.go b/lectures/03-goroutines/du3/main.go new file mode 100644 index 0000000..42b3214 --- /dev/null +++ b/lectures/03-goroutines/du3/main.go @@ -0,0 +1,118 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 250. + +// The du3 command computes the disk usage of the files in a directory. +package main + +// The du3 variant traverses all directories in parallel. +// It uses a concurrency-limiting counting semaphore +// to avoid opening too many files at once. + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sync" + "time" +) + +var vFlag = flag.Bool("v", false, "show verbose progress messages") + +//!+ +func main() { + // ...determine roots... + + //!- + flag.Parse() + + // Determine the initial directories. + roots := flag.Args() + if len(roots) == 0 { + roots = []string{"."} + } + + //!+ + // Traverse each root of the file tree in parallel. + fileSizes := make(chan int64) + var n sync.WaitGroup + for _, root := range roots { + n.Add(1) + go walkDir(root, &n, fileSizes) + } + go func() { + n.Wait() + close(fileSizes) + }() + //!- + + // Print the results periodically. + var tick <-chan time.Time + if *vFlag { + tick = time.Tick(500 * time.Millisecond) + } + var nfiles, nbytes int64 +loop: + for { + select { + case size, ok := <-fileSizes: + if !ok { + break loop // fileSizes was closed + } + nfiles++ + nbytes += size + case <-tick: + printDiskUsage(nfiles, nbytes) + } + } + + printDiskUsage(nfiles, nbytes) // final totals + //!+ + // ...select loop... +} + +//!- + +func printDiskUsage(nfiles, nbytes int64) { + fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9) +} + +// walkDir recursively walks the file tree rooted at dir +// and sends the size of each found file on fileSizes. +//!+walkDir +func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) { + defer n.Done() + for _, entry := range dirents(dir) { + if entry.IsDir() { + n.Add(1) + subdir := filepath.Join(dir, entry.Name()) + go walkDir(subdir, n, fileSizes) + } else { + fileSizes <- entry.Size() + } + } +} + +//!-walkDir + +//!+sema +// sema is a counting semaphore for limiting concurrency in dirents. +var sema = make(chan struct{}, 20) + +// dirents returns the entries of directory dir. +func dirents(dir string) []os.FileInfo { + sema <- struct{}{} // acquire token + defer func() { <-sema }() // release token + // ... + //!-sema + + entries, err := ioutil.ReadDir(dir) + if err != nil { + fmt.Fprintf(os.Stderr, "du: %v\n", err) + return nil + } + return entries +} diff --git a/lectures/03-goroutines/du4/main.go b/lectures/03-goroutines/du4/main.go new file mode 100644 index 0000000..d71204c --- /dev/null +++ b/lectures/03-goroutines/du4/main.go @@ -0,0 +1,145 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 251. + +// The du4 command computes the disk usage of the files in a directory. +package main + +// The du4 variant includes cancellation: +// it terminates quickly when the user hits return. + +import ( + "fmt" + "os" + "path/filepath" + "sync" + "time" +) + +//!+1 +var done = make(chan struct{}) + +func cancelled() bool { + select { + case <-done: + return true + default: + return false + } +} + +//!-1 + +func main() { + // Determine the initial directories. + roots := os.Args[1:] + if len(roots) == 0 { + roots = []string{"."} + } + + //!+2 + // Cancel traversal when input is detected. + go func() { + _, _ = os.Stdin.Read(make([]byte, 1)) // read a single byte + close(done) + }() + //!-2 + + // Traverse each root of the file tree in parallel. + fileSizes := make(chan int64) + var n sync.WaitGroup + for _, root := range roots { + n.Add(1) + go walkDir(root, &n, fileSizes) + } + go func() { + n.Wait() + close(fileSizes) + }() + + // Print the results periodically. + tick := time.Tick(500 * time.Millisecond) + var nfiles, nbytes int64 +loop: + //!+3 + for { + select { + case <-done: + // Drain fileSizes to allow existing goroutines to finish. + for range fileSizes { + // Do nothing. + } + return + case size, ok := <-fileSizes: + // ... + //!-3 + if !ok { + break loop // fileSizes was closed + } + nfiles++ + nbytes += size + case <-tick: + printDiskUsage(nfiles, nbytes) + } + } + printDiskUsage(nfiles, nbytes) // final totals +} + +func printDiskUsage(nfiles, nbytes int64) { + fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9) +} + +// walkDir recursively walks the file tree rooted at dir +// and sends the size of each found file on fileSizes. +//!+4 +func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) { + defer n.Done() + if cancelled() { + return + } + for _, entry := range dirents(dir) { + // ... + //!-4 + if entry.IsDir() { + n.Add(1) + subdir := filepath.Join(dir, entry.Name()) + go walkDir(subdir, n, fileSizes) + } else { + fileSizes <- entry.Size() + } + //!+4 + } +} + +//!-4 + +var sema = make(chan struct{}, 20) // concurrency-limiting counting semaphore + +// dirents returns the entries of directory dir. +//!+5 +func dirents(dir string) []os.FileInfo { + select { + case sema <- struct{}{}: // acquire token + case <-done: + return nil // cancelled + } + defer func() { <-sema }() // release token + + // ...read directory... + //!-5 + + f, err := os.Open(dir) + if err != nil { + fmt.Fprintf(os.Stderr, "du: %v\n", err) + return nil + } + defer f.Close() + + entries, err := f.Readdir(0) // 0 => no limit; read all entries + if err != nil { + fmt.Fprintf(os.Stderr, "du: %v\n", err) + // Don't return: Readdir may return partial results. + } + return entries +} diff --git a/lectures/03-goroutines/lecture.slide b/lectures/03-goroutines/lecture.slide new file mode 100644 index 0000000..e3533e5 --- /dev/null +++ b/lectures/03-goroutines/lecture.slide @@ -0,0 +1,484 @@ +Горутины и каналы +Лекция 4 + +Фёдор Короткий + +* Горутины + + f() // call f(); wait for it to return + go f() // create a new goroutine that calls f(); don't wait + +* Spinner Example + +.play spinner/main.go /func main()/,/END OMIT/ + +* Clock Server + +.play clock1/main.go /func main()/,/^}/ + +* Clock Handler + +.play clock1/main.go /func handleConn/,/^}/ + +* Concurrent Server + +.play clock2/main.go /func main()/,/^}/ + +* Channels + + ch := make(chan int) + +- Канал - это *reference*type*. +- Нулевое значение канала - `nil`. +- Каналы поддерживают две операции: _send_ и _receive_. + + ch <- x // a send statement + x = <-ch // a receive expression in an assignment statement + <-ch // a receive statement; result is discarded + +- `close` закрывает канал. +- _send_ будут паниковать. +- _receive_ будут возвращать значения, которые успели записать в канал, и нулевые значения после этого. + + close(ch) + ch <- x // panic + +* Channel buffer + + ch = make(chan int) // unbuffered channel + ch = make(chan int, 0) // unbuffered channel + ch = make(chan int, 3) // buffered channel + +- Посылка значения по каналу _happens_before_ получения этого значения в другой горутине. + +* Netcat example + +.play netcat3/main.go /func main()/,/^}/ + +* Netcat example + +.play netcat4/main.go /func main()/,/^}/ + +* Pipeline + +.play pipeline1/main.go /func main()/,/^}/ + +* Pipeline termination + +Завершение `Counter` + + go func() { // Counter + for x := 0; x < 100; x++ { + naturals <- x + } + close(naturals) + }() + +* Pipeline termination + +Завершение `Squarer` + + go func() { // Squarer + for { + x, ok := <-naturals + if !ok { + break + } + squares <- x * x + } + close(squares) + }() + +Используя `range`. + + go func() { // Squarer + for x := range naturals { + squares <- x * x + } + close(squares) + }() + +* Unidirectional channel types + + func counter(out chan int) + func squarer(out, in chan int) + func printer(in chan int) + +Явные типы для _send_only_ и _receive_only_ каналов. + + var ch chan int = make(chan int) + var send chan<- int = ch + var recv <-chan int = ch + + func counter(out chan<- int) + func squarer(out chan<- int, int <-chan int) + func printer(in <-chan int) + + func main() { + naturals := make(chan int) + squares := make(chan int) + go counter(naturals) + go squarer(squares, naturals) + printer(squares) + } + +* Buffered channels + + ch := make(chan string, 3) + + ch <- "A" + ch <- "B" + ch <- "C" + + fmt.Println(<-ch) // "A" + + // not very useful in concurrent program + fmt.Println(cap(ch)) // 3 + fmt.Println(len(ch)) // 2 + +* Mirrored query + + func mirroredQuery() string { + responses := make(chan string, 3) + go func() { responses <- request("asia.gopl.io") }() + go func() { responses <- request("europe.gopl.io") }() + go func() { responses <- request("americas.gopl.io") }() + return <-responses // return the quickest response + } + func request(hostname string) (response string) { /* ... */ } + +- Почему нельзя использовать `make(chan string)`? + +* Parallel loop + + // ImageFile reads an image from infile and writes + // a thumbnail-size version of it in the same directory. + // It returns the generated file name, e.g., "foo.thumb.jpg". + func ImageFile(infile string) (string, error) + + // makeThumbnails makes thumbnails of the specified files. + func makeThumbnails(filenames []string) { + for _, f := range filenames { + if _, err := thumbnail.ImageFile(f); err != nil { + log.Println(err) + } + } + } + +* Parallel loop + + // NOTE: incorrect! + func makeThumbnails2(filenames []string) { + for _, f := range filenames { + go thumbnail.ImageFile(f) // NOTE: ignoring errors + } + } + +* Parallel loop + + // makeThumbnails3 makes thumbnails of the specified files in parallel. + func makeThumbnails3(filenames []string) { + ch := make(chan struct{}) + for _, f := range filenames { + go func(f string) { + thumbnail.ImageFile(f) // NOTE: ignoring errors + ch <- struct{}{} + }(f) + } + + // Wait for goroutines to complete. + for range filenames { + <-ch + } + } + +* Parallel loop + + // makeThumbnails4 makes thumbnails for the specified files in parallel. + // It returns an error if any step failed. + func makeThumbnails4(filenames []string) error { + errors := make(chan error) + for _, f := range filenames { + go func(f string) { + _, err := thumbnail.ImageFile(f) + errors <- err + }(f) + } + + for range filenames { + if err := <-errors; err != nil { + return err + } + } + return nil + } + +- Найдите баг?) + +* Parallel loop + + func makeThumbnails5(filenames []string) (thumbfiles []string, err error) { + type item struct { + thumbfile string + err error + } + ch := make(chan item, len(filenames)) + for _, f := range filenames { + go func(f string) { + var it item + it.thumbfile, it.err = thumbnail.ImageFile(f) + ch <- it + }(f) + } + for range filenames { + it := <-ch + if it.err != nil { + return nil, it.err + } + thumbfiles = append(thumbfiles, it.thumbfile) + } + return thumbfiles, nil + } + +* Parallel loop + + func makeThumbnails5(filenames []string) { + var wg sync.WaitGroup + for _, f := range filenames { + wg.Add(1) + go func(f string) { + defer wg.Done() + + _, _ = thumbnail.ImageFile(f) + }(f) + } + wg.Wait() + } + +* Web Crawler + + func crawl(url string) []string { + fmt.Println(url) + list, err := links.Extract(url) + if err != nil { + log.Print(err) + } + return list + } + +* Web Crawler + + func main() { + worklist := make(chan []string) + + // Start with the command-line arguments. + go func() { worklist <- os.Args[1:] }() + + // Crawl the web concurrently. + seen := make(map[string]bool) + for list := range worklist { + for _, link := range list { + if !seen[link] { + seen[link] = true + go func(link string) { + worklist <- crawl(link) + }(link) + } + } + } + } + +- Почему этот код упадёт? + +* Web Crawler + + // tokens is a counting semaphore used to + // enforce a limit of 20 concurrent requests. + var tokens = make(chan struct{}, 20) + func crawl(url string) []string { + fmt.Println(url) + tokens <- struct{}{} // acquire a token + list, err := links.Extract(url) + <-tokens // release the token + if err != nil { + log.Print(err) + } + return list + } + +* Select + + func main() { + fmt.Println("Commencing countdown.") + tick := time.Tick(1 * time.Second) // just example, use time.NewTicker. + for countdown := 10; countdown > 0; countdown-- { + fmt.Println(countdown) + <-tick + } + launch() + } + +В другой горутине: + + abort := make(chan struct{}) + go func() { + os.Stdin.Read(make([]byte, 1)) // read a single byte + abort <- struct{}{} + }() + +* Select + + select { + case <-ch1: + // ... + case x := <-ch2: + // ...use x... + case ch3 <- y: + // ... + default: + // ... + } + +- select блокируется, пока ни один из `case`-ов не может выполниться. + +* Launch Abort + + func main() { + // ...create abort channel... + fmt.Println("Commencing countdown. Press return to abort.") + select { + case <-time.After(10 * time.Second): + // Do nothing. + case <-abort: + fmt.Println("Launch aborted!") + return + } + launch() + } + +* Ticker + + func Tick(d time.Duration) <-chan time.Time { + ch := make(chan time.Time) + go func() { + time.Sleep(d) + ch <- time.Now() + } + return ch + } + +- `time.Tick` нельзя остановить + + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() // cause the ticker's goroutine to terminate + + <-ticker.C // receive from the ticker's channel + +* Non blocking send & receive + + select { + case <-ch: + // ... + default: + // ... + } + + select { + case ch <- "A": + // ... + default: + // ... + } + +* Directory Traversal + +.play du1/main.go /func walkDir()/,/-walkDir/ + +* Directory Traversal + +.play du1/main.go /func main/,/^}/ + +* Directory Traversal Progress + +.play du2/main.go /Print the results/,/^\tprint/ + +* Concurrent Directory Traversal + +.play du3/main.go /fileSizes/,/^\t}\(\)/ + +.play du3/main.go /func walkDir/,/^}/ + +* Concurrent Directory Traversal + +.play du3/main.go /var sema/,/^}/ + +* Cancelation + + var done = make(chan struct{}) + + func cancelled() bool { + select { + case <-done: + return true + default: + return false + } + } + + // Cancel traversal when input is detected. + go func() { + os.Stdin.Read(make([]byte, 1)) // read a single byte + close(done) + }() + +* Cancelation + + func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) { + defer n.Done() + if cancelled() { + return + } + for _, entry := range dirents(dir) { + // ... + } + } + +* Cancelation + + for { + select { + case <-done: + // Drain fileSizes to allow existing goroutines to finish. + for range fileSizes { + // Do nothing. + } + return + case size, ok := <-fileSizes: + // ... + } + } + +* Cancelation + + func dirents(dir string) []os.FileInfo { + select { + case sema <- struct{}{}: // acquire token + case <-done: + return nil // cancelled + } + defer func() { <-sema }() // release token + // ...read directory... + } + +* Chat server + +.play chat/chat.go /func main/,/^}/ + +* Chat broadcaster + +.play chat/chat.go /type client/,/^}/ + +* Chat client + +.play chat/chat.go /func handle/,/END_WRITER/ + diff --git a/lectures/03-goroutines/netcat3/main.go b/lectures/03-goroutines/netcat3/main.go new file mode 100644 index 0000000..3ab89c5 --- /dev/null +++ b/lectures/03-goroutines/netcat3/main.go @@ -0,0 +1,31 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 227. + +// Netcat is a simple read/write client for TCP servers. +package main + +import ( + "io" + "log" + "net" + "os" +) + +func main() { + conn, err := net.Dial("tcp", "localhost:8000") + if err != nil { + log.Fatal(err) + } + + done := make(chan struct{}) + go func() { + _, _ = io.Copy(os.Stdout, conn) + done <- struct{}{} + }() + + _, _ = io.Copy(conn, os.Stdin) + conn.Close() + <-done // wait for background goroutine to finish +} diff --git a/lectures/03-goroutines/netcat4/main.go b/lectures/03-goroutines/netcat4/main.go new file mode 100644 index 0000000..7758307 --- /dev/null +++ b/lectures/03-goroutines/netcat4/main.go @@ -0,0 +1,31 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 227. + +// Netcat is a simple read/write client for TCP servers. +package main + +import ( + "io" + "log" + "net" + "os" +) + +func main() { + conn, err := net.Dial("tcp", "localhost:8000") + if err != nil { + log.Fatal(err) + } + + done := make(chan struct{}) + go func() { + _, _ = io.Copy(os.Stdout, conn) + close(done) // HL + }() + + _, _ = io.Copy(conn, os.Stdin) + conn.Close() + <-done // wait for background goroutine to finish +} diff --git a/lectures/03-goroutines/pipeline1/main.go b/lectures/03-goroutines/pipeline1/main.go new file mode 100644 index 0000000..6f62ac6 --- /dev/null +++ b/lectures/03-goroutines/pipeline1/main.go @@ -0,0 +1,26 @@ +package main + +import "fmt" + +func main() { + naturals := make(chan int) + squares := make(chan int) + + go func() { // Counter + for x := 0; ; x++ { + naturals <- x + } + }() + + go func() { // Squarer + for { + x := <-naturals + squares <- x * x + } + }() + + // Printer (in main goroutine) + for { + fmt.Println(<-squares) + } +} diff --git a/lectures/03-goroutines/pipeline2/main.go b/lectures/03-goroutines/pipeline2/main.go new file mode 100644 index 0000000..6a624fa --- /dev/null +++ b/lectures/03-goroutines/pipeline2/main.go @@ -0,0 +1,32 @@ +package main + +import "fmt" + +func main() { + naturals := make(chan int) + squares := make(chan int) + + go func() { // Counter + for x := 0; x < 100; x++ { + naturals <- x + } + close(naturals) + }() + + go func() { // Squarer + for { + x, ok := <-naturals + if !ok { + break + } + squares <- x * x + } + + close(squares) + }() + + // Printer (in main goroutine) + for x := range squares { + fmt.Println(x) + } +} diff --git a/lectures/03-goroutines/spinner/main.go b/lectures/03-goroutines/spinner/main.go new file mode 100644 index 0000000..20f4260 --- /dev/null +++ b/lectures/03-goroutines/spinner/main.go @@ -0,0 +1,37 @@ +// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan. +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ + +// See page 218. + +// Spinner displays an animation while computing the 45th Fibonacci number. +package main + +import ( + "fmt" + "time" +) + +func main() { + go spinner(100 * time.Millisecond) + const n = 45 + fibN := fib(n) // slow + fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN) +} + +func spinner(delay time.Duration) { + for { + for _, r := range `-\|/` { + fmt.Printf("\r%c", r) + time.Sleep(delay) + } + } +} + +func fib(x int) int { + if x < 2 { + return x + } + return fib(x-1) + fib(x-2) +} + +// END OMIT