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
|
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
|
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 ... }
|
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) }
有疑问加站长微信联系(非本文作者)