天天看點

Go By Example 中文練習

作者:80後運維
  • 通道同步
  • 通道選擇
  • 逾時處理
  • 非阻塞通道
  • 通道關閉
  • 周遊通道
  • 定時器
  • 計時器
  • 工作池
  • 限速
  • 互斥鎖
  • go狀态協程
  • 排序
  • 自定義規則排序
  • 正規表達式
  • 時間戳
  • 數字解析
  • 解析URL位址
  • SHA1散列
  • Scan過濾
  • 指令行參數
  • 環境變量
  • 執行系統指令
  • 系統信号

通道同步

func worker(done chan bool) {
    fmt.Println("working...")
    time.Sleep(2 * time.Second)
    fmt.Println("done")

    done <- true
}

func main() {
    done := make(chan bool, 1)
    go worker(done)
    <-done
}
           

結果:

working...
done
[Finished in 3.1s]
           

通道選擇

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string, 1)
    ch2 := make(chan string, 2)

    go func() {
        time.Sleep(time.Second * 2)
        ch1 <- "one"
    }()

    go func() {
        time.Sleep(time.Second)
        ch2 <- "two"
    }()

    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }
}

           

結果:

$ go run  main.go
two
           

逾時處理

func main() {
    ch1 := make(chan string, 1)

    go func() {
        time.Sleep(time.Second * 1)
        ch1 <- "1"
    }()

    select {
    case res := <-ch1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("time out 1")
    }

    ch2 := make(chan string, 1)
    go func() {
        time.Sleep(time.Second * 2)
        ch2 <- "2"
    }()

    select {
    case res := <-ch1:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("time out 2")
    }
}
           

結果:

$ go run  main.go
1
time out 1
           

非阻塞通道

func main() {
    message := make(chan string)

    select {
    case msg := <-message:
        fmt.Print("message", msg)
    default:
        fmt.Println("no message receive")
    }

    msg := "1"
    select {
    case message <- msg: // 當message通道定義一個緩沖區的時候,這裡可以執行
        fmt.Println("sent message")
    default:
        fmt.Println("no message sent")
    }
}
           

結果

$ go run  main.go
no message receive
no message sent
           

通道關閉

func main() {
    jobs := make(chan int, 5)
    done := make(chan bool)

    go func() {
        for {
            j, ok := <-jobs
            if ok {
                fmt.Println("receive job ", j)
            } else {
                fmt.Println("receive all jobs")
                done <- true      // 通知主程式,已經接受全部任務
                return
            }
        }
    }()

    for i := 1; i < 3; i++ {
        jobs <- i
        fmt.Println("send job", i)
    }

    close(jobs)
    fmt.Println("send all jobs")

    <-done  //等待通知
}
           

結果

$ go run main.go
send job 1
send job 2
send all jobs
receive job  1
receive job  2
receive all jobs
           

周遊通道

func main() {
    queue := make(chan string, 3)

    queue <- "one"
    queue <- "two"

    close(queue)

    for elem := range queue {
        fmt.Println(elem)
    }
}
           

結果:

$ go run main.go
one
two
           

定時器

func main() {
    timer1 := time.NewTimer(time.Second * 2)
    <-timer1.C
    fmt.Println("timer 1 expired")

    timer2 := time.NewTimer(time.Second * 2)

    <-timer2.C
    fmt.Println("timer 2 expired")

    stop2 := timer2.Stop() // 此時timer2已經倒計時結束了,是以不需要停止
    fmt.Println("stop2:", stop2)
    if stop2 {
        fmt.Println("timer 2 stoped")
    }
}
           

結果:

$ go run main.go
timer 1 expired
timer 2 expired
stop2: false
           

上面例子中,因為timer2的倒計時已經停止,timer2.stop()沒有執行,傳回為false,如果想看停止效果,可以改寫代碼:

func main() {
    timer1 := time.NewTimer(time.Second * 2)
    <-timer1.C
    fmt.Println("timer 1 expired")

    timer2 := time.NewTimer(time.Second * 5)
    go func() {
        <-timer2.C
        fmt.Println("timer 2 expired")
    }()

    stop2 := timer2.Stop() 
    fmt.Println("stop2:", stop2)
    if stop2 {
        fmt.Println("timer 2 stoped")
    }
}
           

結果:

$ go run main.go
timer 1 expired
stop2: true
timer 2 stoped
           

可以看到stop2停止了計時器,程式直接退出了。

也可以使用time自帶的after方法實作

func main() {
    ch := make(chan string)

    go func() {
        time.Sleep(time.Second * 2)

        ch <- "result"
    }()

    select {
    case res := <-ch:
        fmt.Println(res)
    case <-time.After(time.Second * 1):
        fmt.Println("timeout")
    }
}
           

結果:

$ go run main.go
timeout

           

計時器

Ticker和timer的差別是,timer倒計時到某一個時間點發送一個信号,而ticker是每隔多長時間發送一個資訊,直到我們手動stop

func main() {
    ticker := time.NewTicker(time.Second)

    go func() {
        for t := range ticker.C {
            fmt.Println("Tick at ", t)
        }
    }()

    time.Sleep(time.Second * 5)
    ticker.Stop()

    fmt.Println("ticker stopped")
}
           

結果:

$ go run main.go
Tick at  2021-05-20 08:55:17.817703 +0800 CST m=+1.003478727
Tick at  2021-05-20 08:55:18.819047 +0800 CST m=+2.004844288
Tick at  2021-05-20 08:55:19.814649 +0800 CST m=+3.000467753
Tick at  2021-05-20 08:55:20.81894 +0800 CST m=+4.004780216
ticker stopped
Tick at  2021-05-20 08:55:21.815348 +0800 CST m=+5.001210115
           

結果是每隔1秒将目前時間作為值push到通道,然後我們循環這個通道擷取值。通過源碼可以看到Ticker.C是一個time類型的channel。

源碼:

type Ticker struct {
    C <-chan Time // The channel on which the ticks are delivered.
    r runtimeTimer
}
           

工作池

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Println("worker", id, "process job", j)
        time.Sleep(time.Second * 2)
        results <- j 
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // 開啟5個程序
    for w := 1; w <= 5; w++ {
        go worker(w, jobs, results)
    }

    // 向通道push任務
    for j := 1; j <= 9; j++ {
        jobs <- j
    }

    close(jobs)

    for r := 1; r <= 9; r++ {
        <-results
    }
}
           

result作用是告知主程序執行結束,當所有的執行結束後,主程序結束退出任務,如果沒有result可能會導緻子程序還沒有結束,主程序就退出了。

結果

worker 3 process id 1
worker 1 process id 2
worker 2 process id 3
worker 1 process id 4
worker 2 process id 6
worker 3 process id 5
worker 2 process id 8
worker 1 process id 9
worker 3 process id 7
           

限速

func main() {

    // 限制每2秒執行一次請求
    ticker := time.Tick(time.Second * 2)
    for i := 1; i <= 5; i++ {
        <-ticker
        fmt.Println("request", i, time.Now())
    }

    // 先向burstylimiter push 3個值
    limiter := make(chan time.Time, 3)
    for i := 0; i < 3; i++ {
        limiter <- time.Now()
    }

    // 然後開啟另外一個線程,每2秒向burstylimiter push 一個值
    go func() {
        for t := range time.Tick(time.Second * 2) {
            limiter <- t
        }
    }()

    // 最後實作效果,前三次沒有限速,最後兩次每2秒執行一次
    for i := 1; i <= 5; i++ {
        <-limiter
        fmt.Println("request", i, time.Now())
    }

}
           

結果:

request 1 2021-05-20 10:09:01.121992 +0800 CST m=+2.005258478
request 2 2021-05-20 10:09:03.117609 +0800 CST m=+4.000918022
request 3 2021-05-20 10:09:05.116884 +0800 CST m=+6.000235109
request 4 2021-05-20 10:09:07.11969 +0800 CST m=+8.003084206
request 5 2021-05-20 10:09:09.119841 +0800 CST m=+10.003278026
request 1 2021-05-20 10:09:09.119978 +0800 CST m=+10.003414895
request 2 2021-05-20 10:09:09.120101 +0800 CST m=+10.003538622
request 3 2021-05-20 10:09:09.12018 +0800 CST m=+10.003616297
request 4 2021-05-20 10:12:29.322124 +0800 CST m=+12.005434486
request 5 2021-05-20 10:12:31.322453 +0800 CST m=+14.005806367

           

效果:前5次,間隔2s,第6-8次,不間隔時間,9-10次再次間隔2s執行。

互斥鎖

func main() {
    var state = make(map[int]int)
    var mutex = &sync.Mutex{}

    for w := 0; w < 10; w++ {
        go func() {
            for {
                key := rand.Intn(5)
                val := rand.Intn(100)
                mutex.Lock()           // 加鎖
                state[key] = val
                mutex.Unlock()         // 解鎖
            }
        }()
    }

    time.Sleep(time.Second)

    fmt.Println("state:", state)

}
           

結果:

$ go run main.go
state: map[0:72 1:25 2:36 3:44 4:38]
           

當去掉互斥鎖配置以後,代碼報錯,因為同時讀寫一塊記憶體位址。

go狀态協程

func main() {

    reads := make(chan *readOp)
    writes := make(chan *writeOp)

    // 通過select選擇來保證同時隻能讀或寫操作
    go func() {
        var state = make(map[int]int)
        for {
            select {
            case read := <-reads:
                read.resp <- state[read.key]
            case writes := <-writes:
                state[writes.key] = writes.val
                writes.resp <- true
            }
        }
    }()

    for r := 0; r < 100; r++ {
        go func() {
            for true {
                read := &readOp{
                    key:  rand.Intn(5),
                    resp: make(chan int),
                }
                reads <- read
            }
        }()
    }

    for w := 0; w < 10; w++ {
        go func() {
            for {
                write := &writeOp{
                    key:  rand.Intn(5),
                    val:  rand.Intn(100),
                    resp: make(chan bool),
                }

                writes <- write
                <-write.resp
            }
        }()
    }

    time.Sleep(time.Second)
}
           

這裡通過select選擇來保證同時隻有一個讀或寫操作,這樣比通過互斥鎖複雜。

排序

func main() {
    strs := []string{"c", "a", "b"}
    sort.Strings(strs)
    fmt.Println("Strings:", strs)

    ints := []int{1, 6, 3, 5, 2}
    sort.Ints(ints)
    fmt.Println("Ints:", ints)
    // 判斷是否排序
    s := sort.IntsAreSorted(ints)
    fmt.Println("Sorted:", s)
}
           

自定義規則排序

以字元串長度排序

type ByLength []string

func (b ByLength) Len() int {
    return len(b)
}

func (b ByLength) Swap(i, j int) {
    b[i], b[j] = b[j], b[i]
}

func (b ByLength) Less(i, j int) bool {
    return len(b[i]) < len(b[j])
}

func main() {
    fruits := []string{"apple", "banana", "kiwi", "orage"}
    sort.Sort(ByLength(fruits))
    fmt.Println(fruits)
}
           

結果

$ go run main.go
[kiwi apple orage banana]
           

正規表達式

func main() {
    match, _ := regexp.MatchString("p([a-z]+)ch", "peach")
    fmt.Println(match)

    r, _ := regexp.Compile("p([a-z]+)ch")

    fmt.Println(r.MatchString("peach"))
    fmt.Println(r.Match([]byte("peach")))

    fmt.Println(r.FindString("peach punch"))
    fmt.Println(r.FindStringIndex("peach punch"))
    fmt.Println(r.FindStringSubmatch("peach punch"))
    fmt.Println(r.FindStringSubmatchIndex("peach punch"))

    fmt.Println(r.FindAllString("peach punch pinch", -1))
    fmt.Println(r.FindAllString("peach punch pinch", 2))
    fmt.Println(r.FindAllString("peach punch pinch", 1))
    fmt.Println(r.FindAllStringSubmatch("peach pinch punch", -1))
    fmt.Println(r.FindAllStringSubmatchIndex("peach pinch punch", -1))

    r = regexp.MustCompile("p([a-z]+)ch")
    fmt.Println(r)
    fmt.Println(r.ReplaceAllString("a peach", "<fruit>"))

    in := []byte("a peach")
    out := r.ReplaceAllFunc(in, bytes.ToUpper)
    fmt.Println(string(out))
}
           

結果:

true
true
true
peach
[0 5]
[peach ea]
[0 5 1 3]
[peach punch pinch]
[peach punch]
[peach]
[[peach ea] [pinch in] [punch un]]
[[0 5 1 3] [6 11 7 9] [12 17 13 15]]
p([a-z]+)ch
a <fruit>
a PEACH
           

時間戳

func main() {
    now := time.Now()
    secs := now.Unix()
    nanos := now.UnixNano()

    fmt.Println(now)
    fmt.Println(secs)
    fmt.Println(nanos)

    fmt.Println(time.Unix(secs, 0))
    fmt.Println(time.Unix(0, nanos))
}
           

結果:

2021-05-21 09:19:47.347155 +0800 CST m=+0.000135175
1621559987
1621559987347155000
2021-05-21 09:19:47 +0800 CST
2021-05-21 09:19:47.347155 +0800 CST
           

數字解析

将字元串解析成對應的數字類型,當遇到無法解析時,會報錯。

func main() {
    f, _ := strconv.ParseFloat("1.234", 64)
    fmt.Println(f)

    i, _ := strconv.ParseInt("1234", 0, 64)
    fmt.Println(i)

    k, _ := strconv.Atoi("135")
    fmt.Println(k)

    _, e := strconv.Atoi("wat")
    fmt.Println(e)
}
           

結果:

1.234
1234
135
strconv.Atoi: parsing "wat": invalid syntax

           

解析URL位址

func main() {
    s := "postgres://user:[email protected]:5432/path?k=v#f"

    u, err := url.Parse(s)
    if err != nil {
        panic(err)
    }

    fmt.Println(u.User.Username())
    p, _ := u.User.Password()
    fmt.Println(p)

    fmt.Println(u.Host)
    fmt.Println(strings.Split(u.Host, ":")[0])
    fmt.Println(strings.Split(u.Host, ":")[1])

    // 擷取路徑
    fmt.Println(u.Path)

    // 擷取參數值
    fmt.Println(u.Fragment)
    fmt.Println(u.RawQuery)
}
           

結果:

$ go run main.go
user
pass
host.com:5432
host.com
5432
/path
f
k=v
           

SHA1散列

func main() {
    s := "sha1 shis string"

    // 建立一個sha1對象
    h := sha1.New()

    // 對字元串處理
    h.Write([]byte(s))

    // sumk可以用來對現有字元串切片追加額外位元組,一般不需要
    bs := h.Sum(nil)

    fmt.Println(s)
    fmt.Printf("%x\n", bs)
}
           

結果:

sha1 shis string
6bf8cad402882fb0fc2aed041dcc79b8e686cfc6

           

Scan過濾

func main() {
    // 從指令行擷取資料
    scanner := bufio.NewScanner(os.Stdin)

    for scanner.Scan() {
        ucl := strings.ToUpper(scanner.Text())
        fmt.Println(ucl)
    }

    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "error", err)
        os.Exit(1)
    }
}
           

結果:

$ echo "nihaoya" |go run main.go
NIHAOYA
           

指令行參數

func main() {
    argsWithProg := os.Args
    argsWithoutProg := os.Args[1:]
    arg := os.Args[3]

    fmt.Println(argsWithProg)
    fmt.Println(argsWithoutProg)
    fmt.Println(arg)
}
           

結果:

[/var/folders/p0/cv96ln_j6t7d6g_lwfwcqgqc0000gn/T/go-build765472776/b001/exe/main a b c d]
[a b c d]
c
           

環境變量

func main() {
    os.Setenv("FOO", "1")
    fmt.Println("FOO:", os.Getenv("FOO"))
    fmt.Println("BAR:", os.Getenv("BAR"))

    for _, e := range os.Environ() {
        pair := strings.Split(e, "=")
        fmt.Println(pair[0])
    }
}
           

結果:

FOO: 1
BAR: 
__INTELLIJ_COMMAND_HISTFILE__
HOME
__CF_USER_TEXT_ENCODING
LOGIN_SHELL
PATH
LC_CTYPE
USER
SSH_AUTH_SOCK
TMPDIR
SHELL
LOGNAME
XPC_SERVICE_NAME
GO111MODULE
GOPATH
XPC_FLAGS
GOROOT
           

執行系統指令

func main() {
    // 執行時間指令
    dateCmd := exec.Command("date")
    dateOut, err := dateCmd.Output()
    if err != nil {
        panic(err)
    }
    fmt.Println(string(dateOut))

    // 執行ls指令
    lsCmd := exec.Command("bash", "-c", "ls -a -l -h")
    lsOut, err := lsCmd.Output()
    if err != nil {
        panic(err)
    }
    fmt.Println(string(lsOut))

    // 執行grep指令
    grepCmd := exec.Command("grep", "hello")

    // 擷取輸入和輸出對象
    grepIn, _ := grepCmd.StdinPipe()
    grepOut, _ := grepCmd.StdoutPipe()

    // 開始執行
    grepCmd.Start()

    // 輸入字元
    grepIn.Write([]byte("hello grep\ngoodbye grep"))
    grepIn.Close()

    // 讀取輸出
    grepBytes, _ := ioutil.ReadAll(grepOut)
    grepCmd.Wait()
    fmt.Println("> grep hello")
    fmt.Println(string(grepBytes))
}
           

結果:

$ go run main.go
Fri May 21 11:33:32 CST 2021

total 160
drwxr-xr-x  17 liangkai  admin   544B May 21 11:33 .
drwxr-xr-x@ 21 liangkai  admin   672B May 17 17:40 ..
-rw-r--r--@  1 liangkai  admin    10K May 17 18:48 .DS_Store
-rw-r--r--   1 liangkai  admin    67B Dec 21 12:08 .env
drwxr-xr-x   8 liangkai  admin   256B May 19 17:47 .idea
drwxr-xr-x   3 liangkai  admin    96B Apr 30 08:13 .vscode
drwxr-xr-x   4 liangkai  admin   128B Apr 30 15:37 Other
drwxr-xr-x   4 liangkai  admin   128B Dec 21 09:13 PracChan
drwxr-xr-x   4 liangkai  admin   128B Apr 12 16:54 PracCodeSvn
drwxr-xr-x   3 liangkai  admin    96B Mar  3 10:59 PracExporter
drwxr-xr-x   7 liangkai  admin   224B Apr 30 15:37 PracHttp
drwxr-xr-x   5 liangkai  admin   160B Dec 21 09:10 PracInterface
drwxr-xr-x   6 liangkai  admin   192B Apr 30 14:46 base
-rw-r--r--   1 liangkai  admin   147B Feb 20 18:44 config.ini
-rw-r--r--   1 liangkai  admin   798B Apr 30 15:41 go.mod
-rw-r--r--   1 liangkai  admin    52K Apr 30 15:41 go.sum
-rw-r--r--@  1 liangkai  admin   725B May 21 11:33 main.go

> grep hello
hello grep

           

系統信号

func main() {

    sigs := make(chan os.Signal, 1)
    done := make(chan bool, 1)

    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        sig := <-sigs
        fmt.Println()
        fmt.Println(sig)
        done <- true
    }()

    fmt.Println("waitting signal")
    <-done
    fmt.Println("exiting")

}
           

結果:

$ go run main.go
waitting signal


^C
interrupt
exiting

           

繼續閱讀