书籍:The Way To Go,第三部分

Error handling

  • error interface

type error interface {
     Error() string
}

  • defining errors :import "errors"

var errNotFound error = errors.New("Not found error")
err := errors.New("math – square root of negative number")
  • custom error

type PathError struct {
     Op string
     Path string
     Err error
}
  • panic and recover

panic("A severe error occurred: stopping the program!")
package parse
// import
type ParseError struct {
    Index int      
    Word  string   
    Error err
}
func (e *ParseError) String() string {
    return fmt.Sprintf("...error parsing %q as int", e.Word)
}
func Parse(input string) (numbers []int, err error) {
      defer func() {
          if r := recover(); r != nil {
              var ok bool
              err, ok = r.(error)
              if !ok {
                  err = fmt.Errorf("pkg: %v", r)
              }
          }
    }()
    fields := strings.Fields(input)
    numbers = fields2numbers(fields) 
    return
}
func fields2numbers(fields []string) (numbers []int) {
    if len(fields) == 0 {
        panic("no words to parse")
    }
    for idx, field := range fields {
        num, err := strconv.Atoi(field)
        if err != nil {
            panic(&ParseError{idx, field, err})
        }
        numbers = append(numbers, num)
    }
    return
}
package main
import (
    "fmt"
    "./parse/parse"
)
func main() {
    var examples = []string{"1 2 3 4 5", ..., }
    for _, ex := range examples {
        fmt.Printf("Parsing %q:\n  ", ex)
            nums, err := parse.Parse(ex)
            if err != nil {
            fmt.Println(err) 
            continue
        }
        fmt.Println(nums)
    }
}

Starting an external command or program

  •  StartProcess

package main
import (                     
     "fmt"                        
     "os/exec"                
     "os"              
)                            
func main() {                
     // os.StartProcess 
     env := os.Environ()      
     procAttr := &os.ProcAttr{
           Env: env,            
           Files: []*os.File{   
               os.Stdin,          
               os.Stdout,         
               os.Stderr,         
           },
     }      
     pid, err := os.StartProcess("/bin/ls", []string{"ls", "-l"}, procAttr)  
     if err != nil {
           fmt.Printf("Error %v starting process!", err) // 
           os.Exit(1)
     }
     fmt.Printf("The process id is %v", pid)
     ...
  • cmd.Run

cmd := exec.Command("gedit")         // this opens a gedit-window
err := cmd.Run()
if err != nil {
     fmt.Printf("Error %v executing command!", err) 
     os.Exit(1)
}
fmt.Printf("The command is %v", cmd)

Testing and Debugging

  • Simulate Breakpoint

where := func() {
     _, file, line, _ := runtime.Caller(1)
     log.Printf("%s:%d", file, line)
}
  • Garbage Collection

runtime.GC()
fmt.Printf("%d\n", runtime.MemStats.Alloc/1024)
runtime.SetFinalizer(obj, func(obj *typeObj))
  • test functions

  1. func (t *T) Fail()

    ü   marks the test function as having failed, but continues its execution.

  2. func (t *T) FailNow()

    ü   marks the test function as having failed and stops its execution;

    ü   all other tests in this file are also skipped, execution continues with the next test file

  3. func (t *T) Log(args …interface{})

    ü   the args are formatted using default formatting and the text  is logged in the error-log

  4. func (t *T) Fatal(args …interface{})

    ü   this has the combined effect of c) followed by b)

  • a concrete example

ü   test programs must be within the same package

ü   the files must have names of the form *_test.go
package main
import (
     "fmt"
     "./even/even"
)
func main() {
     for i:=0; i<=100; i++ {
          fmt.Printf("Is the integer %d even? %v\n", i, even.Even(i)) 
     }
}
package even
func Even(i int) bool {        // Exported functions
     return i%2 == 0
}
func Odd(i int) bool { 
     return i%2 != 0
}
package even
import "testing"
func TestEven(t *testing.T) {
     if !Even(10) {
         t.Log("10 must be even!")
         t.Fail()
     }
     if Even(7) {
         t.Log("7 is not even!")
         t.Fail()
     }
}
func TestOdd(t *testing.T) {
     if !Odd(11) {
         t.Log("11 must be odd!")
         t.Fail()
     }
     if Odd(10) {
         t.Log("10 is not odd!")
         t.Fail()
     }
}
// Using table-driven tests
var tests = []struct{       // Test table
     in  string
     out string
}{
     {"in1", "exp1"},
     {"in2", "exp2"},
     ...
}
func TestFunction(t *testing.T) {
     for i, tt := range tests {
         s := FuncToBeTested(tt.in)
              verify(t, i, "FuncToBeTested: ", tt.in, s, tt.out)
     }
}
func verify(t *testing.T, testnum int, testcase, input, output, expected string)  {
     if input != output {
         t.Errorf("%d. %s with input = %s: output %s != %s", 
                        testnum, testcase, input, output, expected)
     }
}
// investigating performance
// go test –x –v –cpuprofile=cpuprof.out –file x_test.go
// go test –x –v –memprofile=memprof.out –file x_test.go
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
func main() {
     flag.Parse()
     if *cpuprofile != "" {
         f, err := os.Create(*cpuprofile)
         if err != nil {
              log.Fatal(err)
         }
         pprof.StartCPUProfile(f)
         defer pprof.StopCPUProfile()
     }
     ...
// progexec -cpuprofile=progexec.prof
// gopprof progexec progexec.prof
// Some of the interesting commands of this tool are:
// a)    topN: shows the top N samples in the profile
// b)    web or web funcname: writes a graph of the profile data in SVG format
// list funcname or weblist funcname
// If it is seen that the function runtime.mallocgc (which both allocates and runs periodic garbage // collections) is heavily used, then it is time for memory profiling.
// var  memprofile  =  flag.String("memprofile",  "",  "write  memory  profile  to  this file")
// ...
CallToFunctionWhichAllocatesLotsOfMemory()
if *memprofile != "" {
     f, err := os.Create(*memprofile)
     if err != nil {
         log.Fatal(err)
     }
     pprof.WriteHeapProfile(f)
     f.Close()
     return
}
// progexec -memprofile=progexec.mprof
// gopprof progexec progexec.mprof
// gopprof --inuse_objects progexec progexec.mprof
// For web applications:
import _ "http/pprof"
// gopprof http://localhost:6060/debug/pprof/profile # 30-second CPU profile
// gopprof http://localhost:6060/debug/pprof/heap # heap profile

Goroutines

  1. runtime.Gosched() / runtime.Goexit()

  2. An experiential rule of thumb seems to be that for n cores setting GOMAXPROCS  to n-1 yields the best performance, and the following should also be followed:  number of goroutines > 1 + GOMAXPROCS > 1

var numCores = flag.Int("n", 2, "number of CPU cores to use")
runtime.GOMAXPROCS(*numCores)
  • goroutine example 1

func main() {
    go longWait()
    go shortWait()
    fmt.Println("About to sleep in main()")
    time.Sleep(10 * 1e9)  
    fmt.Println("At the end of main()")
}
func longWait() {
    fmt.Println("Beginning longWait()")
    time.Sleep(5 * 1e9)              // sleep for 5 seconds
    fmt.Println("End of longWait()")
}
func shortWait() {
    fmt.Println("Beginning shortWait()")
    time.Sleep(2 * 1e9)              // sleep for 2 seconds
    fmt.Println("End of shortWait()")
}

Channels

var ch1 chan string
ch1 = make(chan string)
ch1 := make(chan string)
buf := 100
ch1 := make(chan string, buf)
chanOfChans := make(chan chan int)
funcChan := chan func()
func main() {
    ch := make(chan string)
    go sendData(ch)
    go getData(ch)
    time.Sleep(1e9)
}
func sendData(ch chan string) {
    ch <- “Washington”
    ch <- “Tripoli”
    ch <- “London”
}
func getData(ch chan string) {
    var input string
    for { 
        input = <-ch; 
        fmt.Printf("%s ", input) 
    }
}
  • Semaphore pattern

type Empty interface {}
var empty Empty
...
data := make([]float64, N)
res := make([]float64, N)
sem := make(chan Empty, N)      // semaphore 
...
for i, xi := range data {
     go func (i int, xi float64) {
           res[i] = doSomething(i,xi)
           sem <- empty
     } (i, xi)
}
for i := 0; i < N; i++ {      // wait for goroutines to finish
     <-sem 
}
  • Channel Factory pattern

func main() {
    stream := pump()
    go suck(stream)        // shortened : go suck( pump() )
    time.Sleep(1e9)
}
func pump() chan int {
    ch := make(chan int)
    go func() {
        for i := 0; ; i++ {
            ch <- i
        }
    }()
    return ch
}
func suck(ch chan int) {
    for {
        fmt.Println(<-ch)
    }
}
func suck(ch chan int) {
    go func() {
        for v := range ch {
            fmt.Println(v)
        }
    }()
}
  • Channel Directionality

// channel can only receive data and cannot be closed
var send_only chan<- int
var recv_only <-chan int        // channel can only send data
...
var c = make(chan int)          // bidirectional
go source(c)
go sink(c)
func source(ch chan<- int) {
    for { ch <- 1 }
}
func sink(ch <-chan int) {
    for { <-ch }
}
...
// closing a channel
func sendData(ch chan string) {
     ch <- "Washington"
     ch <- "Tripoli"
     ch <- "London"
     ch <- "Beijing"
     ch <- "Tokio"
     close(ch)
}
func getData(ch chan string) {
     for {
          input, open := <-ch
          if !open {
               break
          }
          fmt.Printf("%s ", input)
     }
}
  • Switching between goroutines with select

select {
case u:= <- ch1:
     ...
case v:= <- ch2:
 ...
default: // no value ready to be received
  ...
}
  1. if all are blocked, it waits until one can proceed 

  2. if multiple can proceed, it chooses one at random.

  3. when none of the channel operations can proceed and the default clause is present, then this is executed: the default is always runnable (that is: ready to execute). Using a send operation in a select statement with a default case guarantees that the send will be non-blocking!

  • channels with timeouts and tickers

// func Tick(d Duration) <-chan Time
import "time"
rate_per_sec := 10
var dur Duration = 1e8          // rate_per_sec
chRate := time.Tick(dur)        // every 1/10th of a second
for req := range requests {
    <- chRate                   // rate limit our Service.Method RPC calls
    go client.Call("Service.Method", req, ...)
}
// func After(d Duration) <-chan 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)
         }
     }
}
  • 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 failed with %s in %v:”, err, work)
        }
    }()
    do(work)
}


本文来自:开源中国博客

感谢作者:月光独奏

查看原文:书籍:The Way To Go,第三部分

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。