溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Go語(yǔ)言8-goroutine和channel

發(fā)布時(shí)間:2020-07-26 22:38:25 來(lái)源:網(wǎng)絡(luò) 閱讀:1850 作者:騎士救兵 欄目:編程語(yǔ)言

Goroutine

Go語(yǔ)言從語(yǔ)言層面上就支持了并發(fā),這與其他語(yǔ)言大不一樣。Go語(yǔ)言中有個(gè)概念叫做goroutine,這類似我們熟知的線程,但是更輕。

進(jìn)程、線程、協(xié)程

進(jìn)程和線程
進(jìn)程是程序在操作系統(tǒng)中的一次執(zhí)行過(guò)程,系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。
線程是進(jìn)程的一個(gè)執(zhí)行實(shí)體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位。
一個(gè)進(jìn)程可以創(chuàng)建和撤銷多個(gè)線程,同一個(gè)進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行。
所以程序的類型可以分為以下幾種:

  • 一個(gè)進(jìn)程,它只有一個(gè)線程,就是單線程程序
  • 一個(gè)進(jìn)程,它又多個(gè)線程,就是多線程程序
  • 一個(gè)進(jìn)程,它可能還會(huì)fork多個(gè)子進(jìn)程,就是多進(jìn)程程序

并發(fā)和并行的區(qū)別

  • 多線程程序在單核的cou上運(yùn)行,這是并發(fā)(concurrency)。
  • 多線程程序在多個(gè)核的cpu上運(yùn)行(真正的同時(shí)運(yùn)行),這才是并行(parallelism)。

并發(fā),在微觀上,任意時(shí)刻只有一個(gè)程序在運(yùn)行。因?yàn)榫€程已經(jīng)是CPU調(diào)度的最小單元,一個(gè)CPU一次只能處理一個(gè)線程。但是宏觀上這些程序時(shí)同時(shí)在那里執(zhí)行的,所以這個(gè)只是并發(fā)。
所以在python里,貌似講的都是高并發(fā),似乎沒(méi)聽(tīng)過(guò)并行的概念。

協(xié)程和線程
協(xié)程,獨(dú)一的棧空間,共享堆空間,調(diào)度由用戶自己控制。本質(zhì)上類似于用戶級(jí)線程,這些用戶級(jí)線程的調(diào)度也是自己實(shí)現(xiàn)的。
線程,一個(gè)線程上可以跑多個(gè)協(xié)程,協(xié)程是輕量級(jí)的線程。

goroutine 調(diào)度模型

Go的調(diào)度器模型:G-P-M模型。

  • G代表goroutine,它通過(guò)go關(guān)鍵字調(diào)用runtime.newProc創(chuàng)建。
  • P代表processer,可以理解為上下文。
  • M表示machine,可以理解為操作系統(tǒng)的線程。

設(shè)置Golang運(yùn)行的cpu核數(shù)
設(shè)置當(dāng)前的程序運(yùn)行在多少核上,下面的例子是獲取CPU的核數(shù),然后運(yùn)行在所有核上:

package main

import (
    "fmt"
    "runtime"
)

func main() {
    num := runtime.NumCPU()
    runtim.GOMAXPROCS(num)
    fmt.Println(num)
}

上面P的數(shù)目就是這里GOMAXPROCS設(shè)置的數(shù)目,通常設(shè)置為CPU核數(shù)。
1.8版本以上的Golang,是不需要做上面的設(shè)置的,默認(rèn)就是運(yùn)行在所有的核上。當(dāng)然還是可以設(shè)置一下,比如限制只能使用多少核。

goroutine的示例:

package main

import (
    "fmt"
    "time"
)

func example() {
    var i int
    for {
        fmt.Println(i)
        i++
        time.Sleep(time.Millisecond * 30)
    }
}

func main() {
    go example()  // 起一個(gè)goroutine
    var j int
    for j > -100 {
        fmt.Println(j)
        j--
        time.Sleep(time.Millisecond * 100)
    }
    fmt.Println("運(yùn)行結(jié)束")
}

Channel

不同goroutine之間要進(jìn)行通訊,有下面2種方法:

  • 全局變量和鎖同步
  • Channel

先講管道(channel),然后講 goroutine 和 channel 結(jié)合的一些用法。
這篇的channel可以參考下:
https://www.jianshu.com/p/24ede9e90490

全局變量的實(shí)現(xiàn)示例

在下面的例子里定義了變量 m 來(lái)實(shí)現(xiàn)goroutine之間的通訊:

package main

import (
    "fmt"
    "time"
    "sync"
)

var (
    m = make(map[int]uint64)
    lock sync.Mutex
)

type task struct {
    n int
}

func calc(t *task) {
    var res uint64
    res = 1
    for i := 1; i <= t.n; i++ {
        res *= uint64(i)
    }
    lock.Lock()
    m[t.n] = res  // 變量m用來(lái)存放結(jié)果,這樣主線程里就能拿到m的值,操作要加鎖
    lock.Unlock()
}

func main() {
    for i := 0; i < 100; i++ {
        t := &task{i}
        go calc(t)
    }
    for j := 0; j < 10; j++ {
        fmt.Printf("\r已運(yùn)行%d秒", j)
        time.Sleep(time.Second)
    }
    fmt.Println("\r運(yùn)行完畢,輸出結(jié)果:")
    lock.Lock()
    for k, v := range m {
        if v != 0 {
            fmt.Printf("%d! = %v\n", k, v)
        }
    }
    lock.Unlock()
}

channel 概念

channel的概念如下:

  • 類型Unix中的管道(Pipe)
  • 先進(jìn)先出
  • 線程安全,多個(gè)goroutine同時(shí)訪問(wèn),不需要加鎖
  • channel是有類型的,一個(gè)整數(shù)的channel只能存放整數(shù)

channel 聲明
var 變量名 chan 類型

var test1 chan int
var test2 chan string
var tesr3 chan map[string]string
var test4 chan stu
var test5 chan *stu

只是聲明還不夠,使用前還要make,分配內(nèi)存空間:

package main

import "fmt"

func main() {
    var intChan chan int  // 聲明
    intChan = make(chan int, 10)  // 初始化,長(zhǎng)度是10
    intChan <- 10  // 存入管道
    n := <- intChan  // 取出
    fmt.Println(n)
}

定義信號(hào)(空結(jié)構(gòu)體)
有一些場(chǎng)景中,一些 goroutine 需要一直循環(huán)處理信息,直到收到 quit 信號(hào)。作為信號(hào),只需要隨便傳點(diǎn)什么,并不關(guān)注具體的值。那么可以選擇使用空結(jié)構(gòu)體,像下面這樣定義了2個(gè)信號(hào):

msgCh := make(chan struct{})
quitCh := make(chan struct{})
// 傳信號(hào)的方法
msgCh <- struct{}{}  // 前面的 struct{} 是變量的類型,后面的 {} 則是做初始化傳入空值生成實(shí)例
quitCh <- struct{}{}

通過(guò)channel實(shí)現(xiàn)通訊

起一個(gè)goroutine往管道里存,再起一個(gè)goroutine從管道里把數(shù)據(jù)取出:

package main

import (
    "fmt"
    "time"
)

func write(ch chan int) {
    var i int
    for {
        ch <- i
        i ++
        time.Sleep(time.Millisecond)
    }
}

func read(ch chan int) {
    for {
        b := <- ch
        fmt.Println(b)
    }
}

func main() {
    intChan := make(chan int, 10)
    go write(intChan)
    go read(intChan)
    time.Sleep(time.Second * 5)
}

channel 的類型和阻塞

channel 分為不帶緩存的 channel 和帶緩存的 channel。
channel 一定要初始化后才能進(jìn)行讀寫操作,否則會(huì)永久阻塞。這個(gè)不是這里要講的重點(diǎn),順便帶一下。

無(wú)緩存的channle
初始化make的時(shí)候不傳入第二個(gè)參數(shù)設(shè)置容量就是:

ch := make(chan int)

從無(wú)緩存的 channel 中讀取消息會(huì)阻塞,直到有 goroutine 向該 channel 中發(fā)送消息;同理,向無(wú)緩存的 channel 中發(fā)送消息也會(huì)阻塞,直到有 goroutine 從 channel 中讀取消息。

有緩存的 channel
有緩存的 channel 的聲明方式為指定 make 函數(shù)的第二個(gè)參數(shù),該參數(shù)為 channel 緩存的容量:

ch := make(chan int, 10)

有緩存的 channel 類似一個(gè)阻塞隊(duì)列(采用環(huán)形數(shù)組實(shí)現(xiàn))。當(dāng)緩存未滿時(shí),向 channel 中發(fā)送消息時(shí)不會(huì)阻塞,當(dāng)緩存滿時(shí),發(fā)送操作將被阻塞,直到有其他 goroutine 從中讀取消息;
相應(yīng)的,當(dāng) channel 中消息不為空時(shí),讀取消息不會(huì)出現(xiàn)阻塞,當(dāng) channel 為空時(shí),讀取操作會(huì)造成阻塞,直到有 goroutine 向 channel 中寫入消息。

緩沖區(qū)的大小
通過(guò) len 函數(shù)可以獲得 chan 中的元素個(gè)數(shù),通過(guò) cap 函數(shù)可以得到 channel 的緩沖區(qū)長(zhǎng)度。

無(wú)緩存和緩沖區(qū)是1的差別
無(wú)緩存的 channel 的 len和cap 始終都是0。

通過(guò)無(wú)緩存的 channel 進(jìn)行通信時(shí),接收者收到數(shù)據(jù) happens before 發(fā)送者 goroutine 喚醒

上面這句不好理解,不過(guò)可以先看下現(xiàn)象。
下面的這2行函數(shù)會(huì)報(bào)錯(cuò),說(shuō)是死鎖。但是如果設(shè)置了 channel 的容量哪怕是1,就不會(huì)報(bào)錯(cuò)的:

func main() {
    ch := make(chan int)
    ch <- 1
}

雖然容量1的channel也只能存1個(gè)數(shù),但是無(wú)緩沖區(qū)的channel似乎1個(gè)數(shù)都存不了,除非馬上能取走:

func main() {
    ch := make(chan int, 1)
    // 要起一個(gè)goroutine可以馬上接收channel里的數(shù)據(jù)
    go func () {
        fmt.Println(<- ch)
    }()
    ch <- 1
    time.Sleep(time.Second)  // 要給goroutine執(zhí)行完成的時(shí)間
}

小結(jié):無(wú)緩存的channel需要有一個(gè)goroutine可以把channel里的數(shù)據(jù)馬上取走。

channel之間的同步

在學(xué)習(xí)關(guān)閉channel之前,先看下下面的例子。由于沒(méi)有關(guān)閉channel,是會(huì)有問(wèn)題的,不過(guò)例子里都解決了。先看下不用關(guān)閉channel可以怎么搞,然后再接著看關(guān)閉channel的用法:

package main

import (
    "time"
    "fmt"
)

func calc(taskChan chan int, resChan chan int) {
    for v := range taskChan {
        // 判斷是不是素?cái)?shù)
        flag := true
        for i := 2; i < v; i++ {
            if v % i == 0 {
                flag = false
                break
            }
        }
        if flag {
            resChan <- v
        }
    }
}

func main() {
    intChan := make(chan int, 1000)
    // 這個(gè)也是個(gè)goroutine
    go func(){
        for i := 2; i < 100000; i++ {
            intChan <- i
        }
    }()  // 管道滿了之后,這個(gè)匿名函數(shù)會(huì)阻塞,但是不影響程序繼續(xù)往下走

    resultChan := make(chan int, 1000)
    // 同時(shí)起8個(gè)goroutine
    for i := 0; i < 8; i++ {
        go calc(intChan, resultChan)
    }

    // 再起一個(gè)取結(jié)果的goroutine,不阻塞主線程
    go func(){
        for v := range resultChan{
            fmt.Println("素?cái)?shù):", v)
        }
    }()
    // 給上面的匿名函數(shù)幾秒鐘來(lái)輸出結(jié)果
    time.Sleep(time.Second * 5)
}

上面的例子里用了2個(gè)匿名函數(shù),也都是起的goroutine。如果是在主線程里直接for循環(huán)的話,那個(gè)for循環(huán)就會(huì)變成死鎖,程序不會(huì)自己往下走。所以運(yùn)行在goroutine里的死循環(huán),在主線程退出后也就結(jié)束了,不會(huì)有問(wèn)題。后一個(gè)匿名函數(shù)是對(duì)channel的進(jìn)行遍歷,channel取空后,會(huì)進(jìn)入阻塞,如果是運(yùn)行在主線程里的話也會(huì)形成死鎖。
range 遍歷
channel 也可以使用 range 取值,并且會(huì)一直從 channel 中讀取數(shù)據(jù),直到有 goroutine 對(duì)改 channel 執(zhí)行 close 操作,循環(huán)才會(huì)結(jié)束。

關(guān)閉 channel

golang 提供了內(nèi)置的 close 函數(shù)對(duì) channel 進(jìn)行關(guān)閉操作:

ch := make(chan int)
close(ch)

關(guān)于 channel 的關(guān)閉,有以下的特點(diǎn):

  • 關(guān)閉一個(gè)未初始化(nil) 的 channel 會(huì)產(chǎn)生 panic
  • 重復(fù)關(guān)閉同一個(gè) channel 會(huì)產(chǎn)生 panic
  • 向一個(gè)已關(guān)閉的 channel 中發(fā)送消息會(huì)產(chǎn)生 panic
  • 可以從已關(guān)閉的 channel 里繼續(xù)讀取消息,若消息均已讀出,則會(huì)讀到類型的零值。從一個(gè)已關(guān)閉的 channel 中讀取消息不會(huì)阻塞,并且會(huì)返回一個(gè)為 false 的 ok-idiom,可以用它來(lái)判斷 channel 是否關(guān)閉
  • 關(guān)閉 channel 會(huì)產(chǎn)生一個(gè)廣播機(jī)制,所有向 channel 讀取消息的 goroutine 都會(huì)收到消息

有2種方式可以把管道里的數(shù)據(jù)都取出來(lái),但是都需要把管道關(guān)閉:

  • 判斷管道已關(guān)閉并且取完了
  • 遍歷管道

關(guān)閉channel然后讀取的示例:

package main

import "fmt"

func main() {
    var ch chan int
    ch = make(chan int, 5)
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)
    for {
        var b int
        b, ok := <- ch
        fmt.Println(b, ok)
        if ok == false {
            break
        }
    }
}

/* 執(zhí)行結(jié)果
PS H:\Go\src\go_dev\day8\channel\close_chan> go run main.go
0 true
1 true
2 true
3 true
4 true
0 false
PS H:\Go\src\go_dev\day8\channel\close_chan>
*/

上面輸出的最后一條,就是channel已經(jīng)空了,讀出來(lái)的就是類型的0值,并且ok變false了。

遍歷channel的示例:

package main

import "fmt"

func main() {
    var ch chan int
    ch = make(chan int)  // 這個(gè)管道沒(méi)有無(wú)緩存
    // 這個(gè)goroutine一次存一個(gè),再存會(huì)阻塞,直到主線程后面的for循環(huán)遍歷的時(shí)候取走數(shù)據(jù)
    // 存完100個(gè)數(shù)后,這里的for循環(huán)結(jié)束,會(huì)關(guān)閉管道。主線程后面的for循環(huán)的遍歷就能正常結(jié)束了
    go func () {
        for i := 0; i < 100; i++ {
            ch <- i
        }
        close(ch)
    }()
    for v := range ch {
        fmt.Println(v)
    }
}

判斷子線程結(jié)束
學(xué)到這里,再也不需要用Sleep等待子線程結(jié)束了,可以通過(guò)管道實(shí)現(xiàn)??梢詥为?dú)定義一個(gè)專門用來(lái)判斷子線程結(jié)束的管道。子線程完成任務(wù)后,就傳個(gè)值給管道,主線程就阻塞的讀管道里的信息,一旦讀到信息,就說(shuō)明子線程完成了,可以繼續(xù)執(zhí)行或者退出了。如果起了多個(gè)子線程,則主線程就是用for循環(huán)多讀幾次,就能判斷出有多少子線程已經(jīng)結(jié)束了。

channel 只讀、只寫

聲明只讀的channel:

var ch <-chan int

聲明只寫的channel:

var ch chan<- int

應(yīng)用場(chǎng)景,管道需要能夠可讀可寫。但是可以限制它在某個(gè)函數(shù)里的功能,也就是在定義函數(shù)的參數(shù)的時(shí)候,把管道的類型設(shè)置為只讀或只寫。或者把管道傳給結(jié)構(gòu)體,結(jié)構(gòu)體里限制管道的讀寫限制?
下面是之前的一個(gè)例子,僅僅只是把2個(gè)函數(shù)在設(shè)置參數(shù)類型的時(shí)候把管道的讀寫限制加上了:

package main

import (
    "fmt"
    "time"
)

func write(ch chan<- int) {
    var i int
    for {
        ch <- i
        i ++
        time.Sleep(time.Millisecond)
    }
}

func read(ch <-chan int) {
    for {
        b := <- ch
        fmt.Println(b)
    }
}

func main() {
    intChan := make(chan int, 10)
    go write(intChan)
    go read(intChan)
    time.Sleep(time.Second * 5)
}

配合 select 使用

select 用法類似IO多路復(fù)用,可以同時(shí)監(jiān)聽(tīng)多個(gè) channel 的消息狀態(tài),用法如下:

select {
    case <- ch2:
    ...
    case <- ch3:
    ...
    case ch4 <- 10;
    ...
    default:
    ...
}

select 可以同時(shí)監(jiān)聽(tīng)多個(gè) channel 的寫入或讀?。?/p>

  • 若只有一個(gè) case 通過(guò)(不阻塞),則執(zhí)行這個(gè) case 塊
  • 若有多個(gè) case 通過(guò),則隨機(jī)挑選一個(gè) case 執(zhí)行
  • 若所有 case 均阻塞,則執(zhí)行 default 塊。若未定義 default 塊,則 select 語(yǔ)句阻塞,直到有 case 被喚醒
  • 使用 break 會(huì)跳出 select 塊
  • select 不會(huì)循環(huán),就只會(huì)執(zhí)行一個(gè)塊然后就繼續(xù)往后執(zhí)行了

select只會(huì)執(zhí)行一次
這個(gè)例子只會(huì)輸出一次,隨機(jī)是1或者是2,然后接結(jié)束了:

package main

import "fmt"

func main() {
    ch2 := make(chan int, 1)
    ch2 <- 1
    ch3 := make(chan int, 1)
    ch3 <- 2
    select {
    case v := <- ch2:
        fmt.Println(v)
    case v := <- ch3:
        fmt.Println(v)
    default:
        fmt.Println(0)
    }
}

所以如果要把管道里的數(shù)取完,或者取多次,就要再套一層for循環(huán)。

for循環(huán)和break的效果
在select外面用for套了一層死循環(huán),這樣就是反復(fù)的執(zhí)行select。不過(guò)break在這里就沒(méi)效果了:

package main

import (
    "fmt"
     "time"
)

func main() {
    var ch2, ch3 chan int
    ch2 = make(chan int, 10)
    ch3 = make(chan int, 10)
    for i := 0; i < cap(ch2); i++ {
        ch2 <- i
        ch3 <- i * i
    }

    // LABEL1:
    for {
        select {
        case v := <- ch2:
            fmt.Println("ch2", v)
        case v := <- ch3:
            fmt.Println("ch3", v)
        default:
            fmt.Println("所有元素都已經(jīng)取完")
            break  // 這個(gè)break沒(méi)有意義,因?yàn)橹凳翘鰏elect,而不是for循環(huán)
            // break LABEL1  // 這個(gè)break可以直接跳出for循環(huán)
        }
        time.Sleep(time.Second)
    }
}

如果要跳出for循環(huán),可以配合標(biāo)簽。上面的代碼里已經(jīng)寫好了只是注釋掉了。

定時(shí)器

定時(shí)器是在 time 包里的,

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.NewTicker(time.Second)
    for v := range t.C {
        fmt.Println(v)
    }
}

上面調(diào)用的NewTicker()方法返回的是個(gè)結(jié)構(gòu)體,如下:

type Ticker struct {
    C <-chan Time // The channel on which the ticks are delivered.
    // contains filtered or unexported fields
}

上面的例子里遍歷了 t.C 就是一個(gè)channel。time包內(nèi)部應(yīng)該是會(huì)產(chǎn)生一個(gè)goroutine,每隔一段時(shí)間就傳一個(gè)數(shù)據(jù)進(jìn)去。

設(shè)置超時(shí)時(shí)間
還有一個(gè)After()方法,和上面的方法是一樣的。不過(guò)這個(gè)方法直接返回管道,即 NewTimer(d).C 。而NewTimer()方法的管道在返回的結(jié)構(gòu)體的屬性C里。這個(gè)After()方法用起來(lái)更方便。結(jié)合select正好可以做成一個(gè)設(shè)置任務(wù)超時(shí)時(shí)間的功能:

package main

import (
    "fmt"
    "time"
)

func task(ch chan struct{}) {
    time.Sleep(time.Second * 3)
    ch <- struct{}{}
}

func main() {
    ch := make(chan struct{})  // 定義好信號(hào)的管道,傳遞空結(jié)構(gòu)體
    go task(ch)  // 啟動(dòng)一個(gè)任務(wù)
    select {
    case <- ch:
        fmt.Println("任務(wù)執(zhí)行結(jié)束")
    case <- time.After(time.Second * 2):  // 2秒后超時(shí)
        fmt.Println("任務(wù)超時(shí)")
    }
}

goroutine 中使用 recover

程序里起的gorountine中如果panic了,并且這個(gè)goroutine里面沒(méi)有捕獲錯(cuò)誤的話,整個(gè)程序就會(huì)掛掉。
下面的程序會(huì)報(bào)錯(cuò)(Panic),是gorountine里的產(chǎn)生的錯(cuò)誤:

package main

func divideZero(ch chan int) {
    zero := 0
    ch <- 1 / zero
}

func main() {
    ch := make(chan int)
    go divideZero(ch)
    <- ch
}

在gorountine中運(yùn)行錯(cuò)誤了,是可以不影響其他線程和主線程的繼續(xù)執(zhí)行的。所以,好的習(xí)慣是每當(dāng)產(chǎn)生一個(gè)goroutine,就在開(kāi)頭用defer插入recover, 這樣在出現(xiàn)panic的時(shí)候,就只是自己退出而不影響整個(gè)程序。下面是優(yōu)化后的代碼,加入了recover來(lái)捕獲錯(cuò)誤:

package main

import "fmt"

func divideZero(ch chan int) {
    defer func () {
        if err := recover(); err != nil {
            fmt.Println(err)
            // 要給管道傳值,否則主線程從空管道里取值會(huì)阻塞,形成死鎖
            ch <- 0
        }
    }()
    zero := 0
    ch <- 1 / zero
}

func main() {
    ch := make(chan int)
    go divideZero(ch)
    <- ch
}

單元測(cè)試

測(cè)試用例的文件名必須以_test.go結(jié)尾,測(cè)試的函數(shù)也必須以Test開(kāi)頭。符合命名規(guī)則,使用 go test 命令的時(shí)候就能自動(dòng)運(yùn)行測(cè)試用例。
這篇的單元測(cè)試比較粗糙,不過(guò)基本怎么用,以及用法示例都簡(jiǎn)單記下來(lái)了。

被測(cè)試的函數(shù)

先準(zhǔn)備一個(gè)需要被測(cè)試的函數(shù):

package main

import "fmt"

func get_fullname(first, last string) (fullname string) {
    fullname = first + " " + last
    return
} 

func main() {
    fullname := get_fullname("Barry", "Allen")
    fmt.Println(fullname)
}

上面的 get_fullname() 函數(shù)就是接下來(lái)要進(jìn)行單元測(cè)試的函數(shù)。

測(cè)試用例

package main

import "testing"

func TestName(t *testing.T) {
    r := get_fullname("Sara", "Lance")
    expect := "Sara Lance"
    if r != expect {
        t.Fatalf("ERROR: get_fullname expect: %s actual: %s", expect, r)
    }
    t.Log("測(cè)試成功")
}

執(zhí)行測(cè)試

寫完測(cè)試用例,就可以執(zhí)行測(cè)試了,使用命令 go test。輸出如下:

PS H:\Go\src\go_dev\day8\unit_test\name> go test
PASS
ok      go_dev/day8/unit_test/name      0.058s
PS H:\Go\src\go_dev\day8\unit_test\name>

看到PASS了,但是t.Log()并沒(méi)有輸出,要看到更多信息,要用帶上-v參數(shù)。使用命令 go test -v ,輸出如下:

PS H:\Go\src\go_dev\day8\unit_test\name> go test -v
=== RUN   TestName
--- PASS: TestName (0.00s)
        name_test.go:11: 測(cè)試成功
PASS
ok      go_dev/day8/unit_test/name      0.053s
PS H:\Go\src\go_dev\day8\unit_test\name>

直接用go test命令,只顯示測(cè)試的結(jié)果。如果有多個(gè)測(cè)試用例,也只有一個(gè)結(jié)果??梢杂?v參數(shù)看到詳細(xì)的信息,每個(gè)測(cè)試用例的的結(jié)果都會(huì)打印出來(lái)。
如果某個(gè)測(cè)試失敗了,就會(huì)直接退出,不會(huì)繼續(xù)測(cè)試下去。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI