Goroutines lecture
This commit is contained in:
parent
003396081b
commit
a5a33d4eaf
14 changed files with 1316 additions and 0 deletions
93
lectures/03-goroutines/chat/chat.go
Normal file
93
lectures/03-goroutines/chat/chat.go
Normal file
|
@ -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
|
89
lectures/03-goroutines/chat/chat.go.~master~
Normal file
89
lectures/03-goroutines/chat/chat.go.~master~
Normal file
|
@ -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
|
35
lectures/03-goroutines/clock1/main.go
Normal file
35
lectures/03-goroutines/clock1/main.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
35
lectures/03-goroutines/clock2/main.go
Normal file
35
lectures/03-goroutines/clock2/main.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
69
lectures/03-goroutines/du1/main.go
Normal file
69
lectures/03-goroutines/du1/main.go
Normal file
|
@ -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
|
91
lectures/03-goroutines/du2/main.go
Normal file
91
lectures/03-goroutines/du2/main.go
Normal file
|
@ -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
|
||||
}
|
118
lectures/03-goroutines/du3/main.go
Normal file
118
lectures/03-goroutines/du3/main.go
Normal file
|
@ -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
|
||||
}
|
145
lectures/03-goroutines/du4/main.go
Normal file
145
lectures/03-goroutines/du4/main.go
Normal file
|
@ -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
|
||||
}
|
484
lectures/03-goroutines/lecture.slide
Normal file
484
lectures/03-goroutines/lecture.slide
Normal file
|
@ -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/
|
||||
|
31
lectures/03-goroutines/netcat3/main.go
Normal file
31
lectures/03-goroutines/netcat3/main.go
Normal file
|
@ -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
|
||||
}
|
31
lectures/03-goroutines/netcat4/main.go
Normal file
31
lectures/03-goroutines/netcat4/main.go
Normal file
|
@ -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
|
||||
}
|
26
lectures/03-goroutines/pipeline1/main.go
Normal file
26
lectures/03-goroutines/pipeline1/main.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
32
lectures/03-goroutines/pipeline2/main.go
Normal file
32
lectures/03-goroutines/pipeline2/main.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
37
lectures/03-goroutines/spinner/main.go
Normal file
37
lectures/03-goroutines/spinner/main.go
Normal file
|
@ -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
|
Loading…
Reference in a new issue