11 Interfaces and reflection¶
An Interface define a set of methods. And cannot contain variables.
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
}
11.3 type assertion¶
if v, ok := varI.(T); ok {
//
}
Value-received methods can be called with pointer values because they can be dereference first.
package sort
type Sorter interface {
Len() int
Less(i, jint) bool
Swap(i, j int)
}
fucn Sort(data Sorter) {
for pass := 1; pass <data.Len(); pass ++ {
for i :=0; i < data.Len()-pass; i++ {
if data.Less(i+1,i) {
data.Swap(i,i+1)
}
}
}
}
func IsSorted(data Sorter) bool {
n := data.Len()
for i := n-1; i >0; i-- {
if data.Less(i, i-1){
return false
}
}
return true
}
// convenience types for common cases
type IntArray []int
func (p IntArray) Len() int {return len(p)}
func (p IntArray) Less(i,j int) bool {return p[i] < p[j]}
func (p IntArray) Swap(i,j int) {p[i],p[j] = p[j],p[i]}
11.9.2 contsucting an array of a general type or with variables of different types¶
type Element interface{}
type Vector struct {
a []Element
}
func (p *Vector) At(i int) Element {
return p.a[i]
}
func (p *Vector) Set(i int, Element e) {
p.a[i] = e
}
tree struct:
package main
import "fmt"
type Node struct {
le *Node
data interface{}
ri *Node
}
func NewNode(left, right *Node) *Node {
return &Node{left, nil, right}
}
func (n *Node) SetData(data interface{}) {
n.data = data
}
func main() {
root := NewNode(nil, nil)
root.SetData("root node")
a := NewNode(nil, nil)
a.SetData("left node")
b := NewNode(nil, nil)
b.SetData("right node")
root.le = a
root.ri = b
fmt.Printf("%v\n", root)
}
11.10 The reflect package¶
11.14 Structs coolections and higher order functions¶
package main
type Any interface{}
type Car struct {
Model string
Manufacturer string
BuildYear int
}
type Cars []*Car
func (cs Cars) Map(f func(car *Car) Any) []Any {
result := make([]Any, 0)
ix := 0
cs.Process(func(c *Car) {
result[ix] = f(c)
ix++
})
return result
}
allNewBMWs := allCars.FindAll(func(car *Car) bool {
return (car.Manufacturer == "BMW") && (car.BuildYear > 2010)
})
12 Reading and writing¶
// read input from console:
package main
import "fmt"
var (
firstName, lastName string
)
func main() {
fmt.Scanln(*firstName, *lastName)
fmt.Println("Hi %s %s\n", &firstName, &lastName)
}
// or use bufio.Reader
13 Error-handling and Testing¶
no try/catch, defer-panic-and-recover mechanism.
type error interface {
Error() string
}
// to stop an error-state program, use os.Exit(1)
// define new errors
err := errros.New("math-square root of negative number")
// custome error field
type PathError struct {
Op string
Path string
Err error
}
func ( e *PathError) String() string {
return e.Op + " " + e.Path = ": " + e.Err.Error()
}
// making errror-object with fmt
if f < 0 {
return 0, fmt.Errorf("math: square root of negative number %g", f)
}
13.2 Run-time exceptions and panic¶
if panic is called from a nested function, immediately stops the execution of the current function, all defer statement are guaranteed to execute and the control is given to the function caller, which receives this call to panic.
13.3 Recover¶
recover is only useful when called inside a deferred function: it then retrieves the error value passed through the call of panic; when used in normal execution a call to recover will return nil and have no other effects.
like catch in java.
func protect(g func()) {
defer func() {
log.Println("done")
if err := recover(); err != nil {
log.Printf("run time panic : %v", err)
}
}()
log.Println("start")
g()
}
package main
import "fmt"
func badCall() {
panic("bad end")
}
func test() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panicking %s\r\n", e)
}
}()
badCall()
fmt.Printf("After bad call\r\n")
}
func main() {
fmt.Printf("calling test\r\n")
test()
fmt.Printf("calling test end\r\n")
}
13.4 Error-handling and panicking in a custom package¶
best practice:
- always recover from panic in your package: no explicit panic() should be allowed to cross a package boundary
- return errors as error values to the callers of your package.
13.5 An Error-handling scheme with closures¶
func f(a type1, b type2)
fType1 = func f(a type1, b type2)
func errorHandler(fn fType1) fType1 {
return func(a type1, b type2) {
defer func() {
if e, ok := recover().(error); ok {
log.Printf("run time panic:%v", err)
}
}()
fn(a,b)
}
}
13.6 Starting an external command or program¶
os.StartProcess exec.Command(name string, arg …string)
13.7 Testing and benckmarking in GO¶
14 Goroutines and channels¶
14.1 Concurrency, parallelism and goroutines¶
CSP(Communicating Sequentiadl Processes)
parallelism is the ability to make things run quickly by using multiple processors.
An experiential rule of thumb seems to be that for n cores setting GOMAXPROCS to n-1 yields the best the performance, and the following should also be followed: number of goroutines > 1 + GOMAXPROCS > 1
If we do not wai int main(), the program stop the goroutines die with it. When the func main() returns, the program exits, it does not wait for other (non-main) goroutines to complete. The logic of your code must be independent of the order in which goroutines are invoked.
14.1.5¶
difference bewteen goroutines and coroutines(c# and python):
- goroutines imply parallelism, coroutines in general do not
- goroutines communicate via channels; coroutines communicate via yield and resume operations
14.2 Channels for communication between goroutines¶
Using shared variables is not discouranged. Only one goroutine has access to a data item at any given time: so data races cannot occur, by design. Channels a firstclass objects.
var identifier chan datatype // uniniitialized channel is nil
var cha1 chan string // reference type
ch1 = make(chan string)
Channel send and receive operations are aotmic, they always complete without interruption.
14.2.3 Blocking of channels¶
default communication is synchronous and unbuffered: sends do not complete until there is a receiver to accept the value. So channel send/receive block until the other side is ready.
- A send opeartion on a channel blocks until a receiver is available. for the same channel.
- A receive operation for a channel blocks until a sender is available for the same channel.
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
go pump(ch1)
// fmt.Println(<-ch1)
go suck(ch1)
time.Sleep(1e9)
}
func pump(ch chan int) {
for i := 0; ; i++ {
ch <- i
}
}
func suck(ch chan int) {
for {
fmt.Println(<-ch)
}
}
14.2.5 Asynchronous channels-making a channel with a buffer¶
An unbuffered channel can only contain 1 item and is for that reason somethimes too restrictive.
buf := 100
ch1 := make(chan string, buf) //buf is the number of elements the channel can hold
sending to a bufferd channel will not blocked unless buffer is full, reading not blocked unless buffer is empty. If the capacity is greater than 0, channel is asychronous
Semaphore pattern
ch := make(chan int)
go func() {
// do something
ch <- 1
}()
doSomethingElseForAWhile()
<-ch // wait for goroutine to finish. discard send value
14.2.9 Implementing a semaphore using a buffered channel¶
type Empty interface{}
type semaphore chan Empty
sem = make(semaphore, N)
// acauire n resourdces
func (s semaphore) P(n int) {
e := new(Empty)
for i :=0 ; i < n; i ++ {
s <- e
}
}
// release n resources
func (s semaphore) V(n int) {
for i :=0 ;i <n;i ++ {
<-s
}
}
// mutexes
func (s semaphore) Lock() {
s.P(1)
}
func (s semaphore) Unlock() {
s.V(1)
}
// signal-wait
func (s semaphore) Wait(n int) {
s.P(n)
}
func (s semaphore) Signal() {
s.V(1)
}
14.2.10 For-range applied to channels¶
It reads from the given channel ch until the channel is closed and then the code following for continue to execute. Obviously another goroutine must be writing to ch(otherwise the executino blocks in the for-loop) and must close ch when it’s done writing.
producer consumer pattern:
for {
Consume(Product())
}
14.2.11 Channel directionality¶
var send_only chan<- int // channel can only receive data (<-chan T)
var recv_only <-chan int // channel can only send data
IDIOM: Pipe and filter pattern
sendChan := make(chan int)
receiveChan := make(chan string)
go processChannel(sendChan, receiveChan)
func processChannel(in <- chan int, out chan<- string) {
for inValue := range in {
result := // process inValue
out<-result
}
}
// sieve primer number
package main
import "fmt"
func generate() chan int {
ch := make(chan int)
go func() {
for i := 2; ; i++ {
ch <- i
}
}()
return ch
}
func filter(in chan int, prime int) chan int {
out := make(chan int)
go func() {
for {
if i := <-in; i%prime != 0 {
out <- i
}
}
}()
return out
}
func sieve() chan int {
out := make(chan int)
go func() {
ch := generate()
for {
prime := <-ch
ch = filter(ch, prime)
out <- prime
}
}()
return out
}
func main() {
primes := sieve()
for {
fmt.Println(<-primes)
}
}
14.3 Synchronization of goroutine: closing a channel - testing for blocked channels¶
Only the sender should close a channel, never receiver. Sending or Closing a closed channel causes a run-time panic.
if v, ok := <-ch; ok {
process(v)
}
To do a non-blocking channel you need to use a select. for range will automatically detect when the channel is closed.
14.4 Switching between goroutines with select¶
Getting the values out of dirrerent concurrently executing goroutines can be accomplisehd with the select ekyworkd. A select is terminated when a break or return is executed in one of its cases.
- if all are blocked, it waits until one can proceed
- if multiple can proceed, it choose one at random
- when none of the channel operations can proceed and default clause is present, default is always runnable
// sieve primer number
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(2)
ch1 := make(chan int)
ch2 := make(chan int)
go pump1(ch1)
go pump2(ch2)
go suck(ch1, ch2)
time.Sleep(1e9)
}
func pump1(ch chan int) {
for i := 0; ; i++ {
ch <- i * 2
}
}
func pump2(ch chan int) {
for i := 0; ; i++ {
ch <- i + 5
}
}
func suck(ch1 chan int, ch2 chan int) {
for {
select {
case v := <-ch1:
fmt.Printf("received on channel 1 :%d\n", v)
case v := <-ch2:
fmt.Printf("received on channel 2 :%d\n", v)
}
}
}
14.5 Channels, Timeouts and Tickers¶
time.Ticker is an object that repeatedly sends a time value on a contained channel C at a specified time interval.
func main() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
select {
case u := <-ch1:
//...
case v := <-ch2:
//...
case <-ticker.C:
logState(status) // call some logging function logState
default: // novalue ready to be received
//...
}
}
rate limiter:
rate_per_sec := 10
var dur Dration = 1e9
chRate := time.Tick(dur)
for req := range requests {
<- chRate
go client.Call("service.Method", req, ...)
}
time.After(d) only send time once:
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(1e8)
boom := time.After(5e8)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("Boom.")
return
default:
fmt.Println(" .")
time.Sleep(5e7)
}
}
}
14.6 Using recover with goroutines¶
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}
func safelyDo(work *Work) {
defer func() {
if err := recover(); err !=nil {
log.Printf("work faield with %s in %v:", err, work)
}
}()
do(work)
}
14.7 Tasks and Worker processes¶
// Master-worker paradigm
func Worker(in, out chan *Task){
for {
t := <-in
process(t)
out <- t
}
}
when to use: a sync.Mutex or a channel?
-
use locking(mutexes) when:
- caching information in a shared data structure
- holding state information, that is context or status of the running application
-
use channels when:
- communicating asynchronous results
- distributing units of work
- passing owership of data
14.8 Implementing a lazy generator¶
A generator is a function that returns the next value in a sequence each time the function is called.
package main
import (
"fmt"
)
var resume chan int
func intergers() chan int {
yield := make(chan int)
count := 0
go func() {
for {
yield <- count
count++
}
}()
return yield
}
func generateInteger() int {
return <-resume
}
func main() {
resume = intergers()
fmt.Println(generateInteger())
fmt.Println(generateInteger())
fmt.Println(generateInteger())
}
14.9 Implementing Futures¶
future: somtimes you know yopu need to compute a value before you need to actually use the value. in this case, you can potentially start computing the value on another processor and have it ready when you need it.
func InverseProduct(a Matrix, b Matrix) {
a_inv := Inverse(a)
b_inv := Inverse(b)
return Product(a_inv, b_inv)
}
/// a_inv and b_inv can compute parallel
fucn InverseProduct(a Matrix, b Matrix) {
a_inv_future := InverseFuture(a) //. started as a goroutine
b_inv_future := InverseFuture(b)
a_inv := <-a_inv_future
b_inv := <-b_inv_future
return Product(a_inv,b_inv)
}
func InverseFuture(a Matrix) {
future := make(chan Matrix)
go func() { future<-Inverse(a) } () // launched a closure as a goroutine
return future
}
14.10 Multiplexing¶
14.11 Limiting the number of requests processed concurrently¶
14.12 Chaining goroutines¶
14.13 Parallelzing a computation over a number of cores¶
func DoAll() {
sem := make(chan int, NCPU)
for i := 0; i < NCPU; i ++ {
go DoPart(sem)
}
// Drain the channel sem, waiting for NCPU tasks to complete
for i:= 0; i < NCPU; i ++ {
<-sem
}
// All done
}
func DoPart(sem chan int) {
// do the part of the computation
sem <- 1 // signal that thie piece is done
}
func main() {
runtime.GOMAXPROCS = NCPU
DoAll()
}
14.14 Parallelizing a computation over a large amount of data¶
a number of steps: Preprocess / StepA / StepB / … / PostProcess
pipelining algorithm:
func SerialProcessData(in <- chan *Data, out <- chan *Data) {
for data := range in {
tmpA := PreprocessData(data)
tmpB := ProcessStepA(tmpA)
tmpC := ProcessStepA(tmpB)
out <- PostProcess(tmpC)
}
}
func ParallelProcessData(in <- chan *Data, out <- chan *Data) {
// make channels:
preOut := make(chan *Data, 100)
stepAOut := make(chan *Data, 100)
stepBOut := make(chan *Data, 100)
stepCOut := make(chan *Data, 100)
// start parallel computations
go PreprocessData(in, preOut)
go ProcessStepA(preOut, stepAOut)
go ProcessStepB(stepAOut, stepBOut)
go ProcessStepC(stepBOut, stepCOut)
go PostProcessData(stepOut, out)
}
14.15 The leaky bucket algorithm¶
var freeList = make(chan *Buffer, 100)
var serverChan = make(chan *Buffer)
func client() {
for {
var b *Buffer
select {
case b = <-freeList:
// Got one; nothing more todo
default:
b = new(Buffer)
loadInto(b) // read next message from the network
}
serverChan <- b //send to server
}
}
func server() {
for {
b := <-serverChan // wait for work
process(b)
// reuse buffer is threre's room
select {
case freeList <- b:
// Reuse buffer if free slot on freeList; nothing more to do
default:
// freeList is full, just carry on : the buffer is 'dropped'
// doesn't work when freeList is full, leaky bucket
}
}
}
14.16 Benchmarking goroutines¶
14.17 Concurrent access to object using a chnnel¶
To safeguard concurrent modifications of an object instead of using locking with a sync Mutex, we can also use a backend goroutine for the sequential execution of anonymous functions.
16 Common Go Pitfalls or Mistakes¶
16.1 Hiding(shadowing) a variable by misusing short declaration¶
var remeber bool = false
if somehting {
remeber := true // use = not :=
}
// use member
func shadow() (err error) {
x, err := check1() // x is created, err is assigned to
if err != nil {
return // err correctly returned
}
if y, err := check2(x); err !=nil { //y and inner err are created
return // inner err shadows err so nil is wrongly returned!
} else {
fmt.Println(y)
}
return
}
16.2 Misusing strings¶
mind that strings in go(like java and python) are immutable, Do not use a + b in a for loop, intead one should use a bytes.Buffer to accumulate string content.
func shadow() (err error) {
x, err := check1() // x is created, err is assigned to
if err != nil {
return // err correctly returned
}
if y, err := check2(x); err !=nil { //y and inner err are created
return // inner err shadows err so nil is wrongly returned!
} else {
fmt.Println(y)
}
return
}
16.3 Using defer for closing a file in the wrong scope¶
Defer is only executed at the return of a function, not at the end of a loop or some other limited scope.
16.4 Confusing new() and make()¶
- for slices, maps and channels, use make
- for arrays, structs and all value types: use new
16.5 No need to pass a pointer to a slice to a function¶
16.6 Using pointers to interface types¶
Never use a pointer to an interface type, this is already a pointer!
16.7 Misusing pointers wiht value types¶
16.8 Misusing goroutines and channels¶
Only using goroutines and channels only where concurrency is important!
16.9 Using closures with goroutines¶
package main
import (
"fmt"
"time"
)
var values = [5]int{10, 11, 12, 13, 14}
func main() {
// version A
for ix := range values {
func() {
fmt.Print(ix, " ")
}()
}
fmt.Println() // 0,1,2,3,4
// version B
for ix := range values {
go func() { // invoke each closure as a goroutine
fmt.Print(ix, " ")
}() // the goroutine will probably not begin executing until after the loop
}
fmt.Println() // 4,4,4,4,4
time.Sleep(5e9)
// Version C: the right way
for ix := range values {
go func(ix interface{}) { // invoke each closure with ix as a parameter
fmt.Print(ix, " ") //ix is the evaluated at each iteration and placed on the stack of goroutine
}(ix)
}
fmt.Println() // random
time.Sleep(5e9)
// Version D: print out values
for ix := range values {
val := values[ix] // variables declared witin the body of a loop are not shared between iterations
go func() {
fmt.Print(val, " ")
}()
}
time.Sleep(1e9) //10,11,12,13,14
}
16.10 Bad error handling¶
wrap your error conditions in a closure wherever possible, like in the following example.
// seperate error checking, error reporting, and normal program logic
func httpRequestHandler(w http.ResponseWriter, req *http.Request) {
err := func() error {
if req.Method != "GET"{
return errors.New("expected GET")
}
if input := paraseInput(req); input != "command" {
return errors.New("malformed command")
}
}()
// other error conditions can be tested here
if err != nil {
w.WriteHeader(400)
io.WriterString(w, err)
return
}
doSomething() //
}
17 Go Language Patterns¶
17.1 The comma, ok pattern¶
17.2 The defer pattern¶
17.3 The visibility pattern¶
17.4 The operator pattern and interface¶
go不支持操作符重载,可以使用函数/方法/接口 来模拟。