22-Go語言管道(Channel)

多線程同步問題

  • 互斥鎖
    • 互斥鎖的本質(zhì)是當一個goroutine訪問的時候, 其它goroutine都不能訪問
    • 這樣就能實現(xiàn)資源同步, 但是在避免資源競爭的同時也降低了程序的并發(fā)
      性能,程序由原來的并發(fā)執(zhí)行變成了串行
  • 打印案例
    • 沒有添加互斥鎖, 那么兩個人都有機會輸出自己的內(nèi)容
    • 添加互斥鎖,只有當一個輸出完畢的時候,另一個才能輸出
package main

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

//創(chuàng)建一個互斥鎖
//可以讓程序從并發(fā)狀態(tài)變成并行狀態(tài)
var lock = sync.Mutex{}

//定義一個打印字符的函數(shù)
func myprint(str string)  {
    //添加鎖
    lock.Lock()
    for _, value := range str {
        time.Sleep(time.Microsecond *300)
        fmt.Printf("%c", value)
    }

    //解鎖
    lock.Unlock()
}

//調(diào)用者1
func person1()  {
    myprint("hello")
}

//調(diào)用者2
func person2()  {
    myprint("world")
}
func main() {
    //開啟go程
    go person1()
    go person2()

    //保證主線程不退出,程序不結(jié)束
    for  {
        ;
    }
}

生產(chǎn)者與消費者

  • 生產(chǎn)者消費者模型
    • 某個模塊(函數(shù))負責(zé)生產(chǎn)數(shù)據(jù), 這些數(shù)據(jù)由另一個模塊來負責(zé)處理
    • 一般生產(chǎn)者消費者模型包含三個部分生產(chǎn)者、緩沖區(qū)、消費者
    • 沒有緩沖區(qū),消費者發(fā)生變化, 會直接影響生產(chǎn)者, 耦合性太強
    • 添加緩沖區(qū)可以提高效率

生產(chǎn)者和消費者資源競爭問題

  • 生產(chǎn)者生產(chǎn)產(chǎn)比較慢, 而消費比較快, 就會導(dǎo)致消費者消費到錯誤數(shù)據(jù)
package main

import (
    "fmt"
    "math/rand"
    "time"
)

//定義數(shù)組模擬緩沖區(qū)
var arr [10]int

//定義模擬生產(chǎn)者函數(shù)
func producter()  {
    //定義隨機因子
    rand.Seed(time.Now().UnixNano())
    //產(chǎn)生隨機數(shù)
    for i := 0;i < 10 ;i++  {
        num := rand.Intn(100)
        fmt.Println("生產(chǎn)者生產(chǎn)了", num)
        //將生產(chǎn)的數(shù)據(jù)放入緩沖區(qū)中
        arr[i] = num
        time.Sleep(time.Millisecond * 300)

    }
}

//定義函數(shù)模擬消費者
func consumer(){
    for i := 0;i < 10 ;i++  {
        value := arr[i]
        fmt.Println("------消費者消費了",value)
    }
}

func main() {
    // 我們想要的是, 只有生產(chǎn)者生產(chǎn)了, 我們才能消費
    // 注意點: 在多go程中, 如果生產(chǎn)者生產(chǎn)的太慢, 那么消費者就會消費到錯誤的數(shù)據(jù)
    go producter()
    // 注意點: 看上去通過給生產(chǎn)者以及消費者同時加鎖就能解決, 只有生產(chǎn)完了才能消費
    //         但是取決于誰想執(zhí)行加鎖操作, 所以不完美
    go consumer()

    for  {
        ;
    }
}
  • 利用互斥鎖解決問題
package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)
//創(chuàng)建一把互斥鎖
var lock = sync.Mutex{}

//定義數(shù)組模擬緩沖區(qū)
var arr [10]int

//定義模擬生產(chǎn)者函數(shù)
func producter()  {
    /*
    為什么在生產(chǎn)者和消費者中都上鎖之后, 就可以實現(xiàn)生產(chǎn)完再消費?
    因為生產(chǎn)者和消費者中的鎖都是同一把, 都是全局變量lock

    調(diào)用Lock()函數(shù)的作用: 是修改Mutex結(jié)構(gòu)體中的state屬性的值, 將它改為一個非0的值
    每次上鎖的時候都會判斷有沒有被鎖定, 如果已經(jīng)鎖定就不鎖了, 并且不會執(zhí)行后面的代碼
    調(diào)用Unlock函數(shù)的作用: 是修改Mutex結(jié)構(gòu)體中的state屬性的值, 將它改為0
     */

    //上鎖
    // 這里鎖定的是當前go程, 也就是當前函數(shù)
    // 意味著其它的go程不能執(zhí)行當前的函數(shù), 只有當前鎖定這個函數(shù)的go程才能執(zhí)行這個函數(shù)
    lock.Lock()
    //定義隨機因子
    rand.Seed(time.Now().UnixNano())
    //產(chǎn)生隨機數(shù)
    for i := 0;i < 10 ;i++  {
        num := rand.Intn(100)
        fmt.Println("生產(chǎn)者生產(chǎn)了", num)
        //將生產(chǎn)的數(shù)據(jù)放入緩沖區(qū)中
        arr[i] = num
        //time.Sleep(time.Millisecond * 300)
    }
    //解鎖
    lock.Unlock()
}

//定義函數(shù)模擬消費者
func consumer(){
    //上鎖
    lock.Lock()
    for i := 0;i < 10 ;i++  {
        value := arr[i]
        fmt.Println("------消費者消費了",value)
    }

    //解鎖
    lock.Unlock()
}

func main() {
    // 我們想要的是, 只有生產(chǎn)者生產(chǎn)了, 我們才能消費
    // 注意點: 在多go程中, 如果生產(chǎn)者生產(chǎn)的太慢, 那么消費者就會消費到錯誤的數(shù)據(jù)
    go producter()
    // 注意點: 看上去通過給生產(chǎn)者以及消費者同時加鎖就能解決, 只有生產(chǎn)完了才能消費
    //         但是取決于誰想執(zhí)行加鎖操作, 所以不完美
    go consumer()

    for  {
        ;
    }
}

Go語言管道

管道的基本使用
  • Channel的本質(zhì)是一個隊列
  • Channel是線程安全的, 也就是自帶鎖定功能
  • Channel聲明和初始化
    • var 變量名稱 chan 數(shù)據(jù)類型
    • make(chan 數(shù)據(jù)類型, 容量)
    • 管道和切片/字典一樣,必須創(chuàng)建后才能使用,否則會報錯
    • Channel和切片還有字典一樣, 是引用類型,是地址傳遞
package main

import "fmt"

func main() {
    /*
    1.什么是管道:
    管道就是一個隊列, 具備先進先出的原則
    是線程安全的, 也就是自帶鎖定功能

    2.管道作用:
    在Go語言的協(xié)程中, 一般都使用管道來保證多個協(xié)程的同步, 或者多個協(xié)程之間的通訊

    3.如何聲明一個管道, 和如何創(chuàng)建一個管道
    管道在Go語言中和切片/字典一樣也是一種數(shù)據(jù)類型
    管道和切片/字典非常相似, 都可以用來存儲數(shù)據(jù), 都需要make之后才能使用
    3.1管道聲明格式:
    var 變量名稱 chan 數(shù)據(jù)類型
    var myCh chan int
    如上代碼的含義: 聲明一個名稱叫做myCh的管道變量, 管道中可以存儲int類型的數(shù)據(jù)
    3.2管道的創(chuàng)建:
    make(chan 數(shù)據(jù)類型, 容量)
    myCh = make(chan int, 3);
    路上代碼的含義: 創(chuàng)建一個容量為3, 并且可以保存int類型數(shù)據(jù)的管道

    4.管道的使用
    4.1如何往管道中存儲(寫入)數(shù)據(jù)?
    myCh<-被寫入的數(shù)據(jù)
    4.2如何從管道中獲取(讀取)數(shù)據(jù)?
    <-myCh
    對管道的操作是IO操作
    例如: 過去的往文件中寫入或者讀取數(shù)據(jù), 也是IO操作
    例如: 過去的往屏幕上輸出內(nèi)容, 或者從屏幕獲取內(nèi)容, 也是IO操作
    stdin / stdout / stderr

    注意點:
    和切片不同, 在切片中make函數(shù)的第二個參數(shù)表示的切片的長度(已經(jīng)存儲了多少個數(shù)據(jù)),
    而第三個參數(shù)才是指定切片的容量
    但是在管道中, make函數(shù)的第二個參數(shù)就是指定管道的容量, 默認長度就是0
     */

     //1.定義一個管道
     //var myChan chan int
     //2.使用make創(chuàng)建管道
     myChan := make(chan int, 3)
     //3.往管道中存儲數(shù)據(jù)
     myChan<-1
     myChan<-2
     myChan<-3
     //從管道中取出數(shù)據(jù)
     fmt.Println(<-myChan)
     fmt.Println(<-myChan)
     fmt.Println(<-myChan)
     

    //定義一個管道
    var myChan chan int
    //直接使用管道
    //注意點: 會報錯,管道定義完成后不創(chuàng)建是無法直接使用的
    //myChan<-666
    //fmt.Println(<-myChan)

    //創(chuàng)建管道
    myChan = make(chan int, 3)
    //只要往管道中寫入了數(shù)據(jù), 那么len就會增加
    myChan <- 2
    //fmt.Println("len = ", len(myChan), "cap = ", cap(myChan))
    myChan <- 4
    //fmt.Println("len = ", len(myChan), "cap = ", cap(myChan))
    myChan <- 6
    //fmt.Println("len = ", len(myChan), "cap = ", cap(myChan))
    //注意點: 如果len等于cap, 那么就不能往管道中再寫入數(shù)據(jù)了, 否則會報錯
    //myChan <- 8

    //管道未寫入數(shù)據(jù),使用管道去取數(shù)據(jù)會報錯
    //從管道中取數(shù)據(jù),len會減少
    //<-myChan
    fmt.Println(<-myChan)
    fmt.Println("len=",len(myChan),"cap = ", cap(myChan))
    fmt.Println(<-myChan)
    fmt.Println("len=",len(myChan),"cap = ", cap(myChan))
    fmt.Println(<-myChan)
    fmt.Println("len=",len(myChan),"cap = ", cap(myChan))
    //注意點: 取數(shù)據(jù)個數(shù)也不可以超出寫入的數(shù)據(jù)個數(shù),否則會報錯
    //fmt.Println(<-myChan)
}

管道的遍歷和關(guān)閉
  • 管道遍歷推薦兩種方式
    • for..range方法遍歷
    • for死循環(huán)遍歷
package main

import "fmt"

func main() {
    /*
    管道的遍歷:
    可以使用for循環(huán), 也可以使用 for range循環(huán), 以及死循環(huán)來遍歷
    但是更推薦使用后兩者

    因為在企業(yè)開發(fā)中, 有可能我們不知道管道中具體有多少條數(shù)據(jù), 所以如果利用for循環(huán)來遍歷, 那么無法確定遍歷的次數(shù), 并且如果遍歷的次數(shù)太多, 還會報錯
     */

    //創(chuàng)建一個管道
    myChan := make(chan int, 3)
    //往管道中寫入數(shù)據(jù)
    myChan <- 2
    myChan <- 4
    myChan <- 6
    //注意點: 如果不關(guān)閉管道,遍歷管道會報錯
    close(myChan)
    //第一種方法遍歷管道
    //for value := range myChan {
    //  fmt.Println(value)
    //}

    //第二種方法遍歷管道
    //注意點: 如果被遍歷的管道沒有關(guān)閉, 那么會報錯
    //        如果管道沒有被關(guān)閉, 那么會將true返回給ok, 否則會將false返回給Ok
    for {
        if v,ok := <-myChan; ok {
            fmt.Println(v)
            fmt.Println(ok)
        }else {
            break
        }
    }


    //注意點: 管道關(guān)閉后無法往里面寫入數(shù)據(jù),會報錯,但是可以讀取數(shù)據(jù)不會報錯
    myChan := make(chan int,3)
    close(myChan)
    //往管道中寫入數(shù)據(jù)
    //myChan<-1
    //myChan<-2
    //myChan<-3
    //從管道中讀取數(shù)據(jù)
    <-myChan
}

管道阻塞現(xiàn)象
  • 阻塞現(xiàn)象只會發(fā)生在go程中運行才會發(fā)生,在普通線程中不會發(fā)生這種現(xiàn)象
package main

import "fmt"

//定義一個管道
var myChan = make(chan int,2)

func test()  {
    /*myChan<-1
    myChan<-2
    fmt.Println("管道滿之前的代碼")
    //這里不會報錯, 會阻塞, 等到將管道中的數(shù)據(jù)讀出去之后, 有的新的空間再往管道中寫
    myChan<-3
    fmt.Println("管道滿之后的代碼")*/
    //這里不會報錯,會阻塞,等到有人往管道中寫入數(shù)據(jù)之后,有新的數(shù)據(jù)之后才會讀取
    fmt.Println("讀取之前的代碼")
    <-myChan
    fmt.Println("讀取之后的代碼")
}

func main() {
    /*
    單獨在主線程中操作管道, 寫滿了會報錯, 沒有數(shù)據(jù)去讀取也會報錯
    只要在go程中操作管道, 無論有沒有寫滿, 無論有沒有數(shù)據(jù)都會發(fā)生管道阻塞的現(xiàn)象
    */
    go test()
    for  {
        ;
    }
}
  • 利用管道阻塞實現(xiàn)并發(fā)變串行
package main

import (
    "fmt"
    "time"
)
//創(chuàng)建一個管道
var myChan = make(chan bool)


func printer(str string)  {
    for _, value := range str {
        fmt.Printf("%c", value)
        time.Sleep(time.Microsecond * 300)
    }
}

func person1()  {
    printer("hello")
    //往管道中寫入數(shù)據(jù)
    //只有printer函數(shù)執(zhí)行完,才會往管道中寫入數(shù)據(jù)
    myChan<-true
}

func person2()  {
    //從管道中讀取數(shù)據(jù)
    //只有管道中有數(shù)據(jù)才會讀取,否則會阻塞
    <-myChan
    printer("world")
}

func main() {
    go person1()
    go person2()
    for  {
        ;
    }
}
  • 利用管道阻塞實現(xiàn)生產(chǎn)者和消費者模型
package main

import (
    "fmt"
    "math/rand"
    "time"
)

//定義管道模擬緩沖區(qū)
var myChan = make(chan int, 10)

//定義生產(chǎn)者函數(shù)
func producter() {
    //定義隨機因子
    rand.Seed(time.Now().UnixNano())
    //生成隨機數(shù)
    for i := 0; i < 10; i++ {
        num := rand.Intn(100)
        fmt.Println("生產(chǎn)者生產(chǎn)了", num)
        //將生產(chǎn)的數(shù)據(jù)存入到管道中
        myChan <- num
    }
}

//定義函數(shù)模擬消費者
func customer() {
    //從管道中讀取數(shù)據(jù)
    for i := 0; i < 10; i++ {
        num := <-myChan
        fmt.Println("----消費者消費了", num)
    }

}
func main() {
    //創(chuàng)建兩個go程
    //多個生產(chǎn)者和多個消費者
    go producter()
    go producter()


    go customer()
    go customer()

    for {
        ;
    }
}

無緩沖區(qū)管道
  • 管道容量為0的管道就是無緩沖區(qū)管道
package main

import "fmt"

func main() {
    /*
    // 管道總結(jié):
    // 管道一般都在go程中使用, 不會直接在主線程中使用, 無論是有緩沖的還是沒有緩沖的
    // 只要是在go程中使用, 無論是有緩沖的還是沒有緩沖的, 都會出現(xiàn)阻塞現(xiàn)象

    */
    //創(chuàng)建無緩沖管道
    // 注意點:
    // 沒有緩沖的管道不能直接存儲數(shù)據(jù)
    // 沒有緩沖的管道不能直接獲取數(shù)據(jù)
    // 注意點:
    // 想使用沒有緩沖的管道, 必須保證讀和寫同時存在, 而且還必須保證讀和寫是在不同的go程中,至少有一個在go程中
    // 并且讀必須寫在寫的前面
    //myChan := make(chan int,0)
    // 沒有緩沖的管道不能直接獲取數(shù)據(jù)
    //fmt.Println(<-myChan)
    // 沒有緩沖的管道不能直接存儲數(shù)據(jù)
    //myChan<-1

    /*//讀取管道在go程中
    go func() {
        fmt.Println("讀之前的代碼")
        fmt.Println(<-myChan)
        fmt.Println("讀之前的代碼")

    }()

    //寫入管道在主線程中
    func(){
        time.Sleep(time.Second * 5)
        fmt.Println("寫之前的代碼")
        myChan<-2
        fmt.Println("寫之后的代碼")
    }()*/


    //無緩沖管道單個使用
    //如果是在go程中使用無緩沖的管道, 那么就可以單個使用(只有讀, 或者只有寫)
    myChan := make(chan int,0)
    go func() {
        //只讀不會報錯
        //<-myChan
        //只寫不會報錯
        fmt.Println("寫之前的代碼")
        myChan<-1
        fmt.Println("寫之后的代碼")
    }()

    for  {
        ;
    }
}
  • 利用無緩沖區(qū)管道解決主線程結(jié)束問題
package main

import "fmt"

func main() {
    //創(chuàng)建一個有緩沖管道
    myChan := make(chan int,3)
    //創(chuàng)建一個無緩沖管道
    exitChan := make(chan bool,0)

    //往管道中存儲數(shù)據(jù)
    go func() {
        for i := 0; i < 3; i++ {
            myChan<-i
            fmt.Println("生產(chǎn)了",i)
        }
        //無緩沖管道存儲數(shù)據(jù)
        exitChan<-true
    }()

    <-exitChan
    //for  {
    //  ;
    //}
}

單向管道
  • 默認情況下所有的管道都是雙向的管道(可讀可寫),那么在企業(yè)開發(fā)中, 我們可能會需要將管道作為函數(shù)的參數(shù), 并且還需要限制函數(shù)中如何使用管道,,那么這個時候我們就可能會使用單向管道
  • 雙向管道格式
    • var myCh chan int
  • 單向管道格式
    • var myCh chan<- int; 只寫的管道
    • var myCh <-chan int; 只讀的管道
  • 注意點:
    • 雙向管道可以轉(zhuǎn)換為單向的管道
    • 但是單向的管道不能轉(zhuǎn)換為雙向的管道
package main

import "fmt"

func main() {
    /*
    默認情況下所有的管道都是雙向的管道(可讀可寫)
    那么在企業(yè)開發(fā)中, 我們可能會需要將管道作為函數(shù)的參數(shù), 并且還需要限制函數(shù)中如何使用管道,
    那么這個時候我們就可能會使用單向管道

    雙向格式:
    var myCh chan int;
    myCh = make(chan int, 5)
    myCh = make(chan int)

    單向格式:
    var myCh chan<- int; 只寫的管道
    var myCh <-chan int; 只讀的管道

    雙向管道可以轉(zhuǎn)換為單向的管道
    但是單向的管道不能轉(zhuǎn)換為雙向的管道
    */

    //定義一個雙向管道
    myChan := make(chan int,3)
    //定義一個只讀的單向管道
    var myChan1 <-chan int
    //定義一個只寫的單向管道
    //var myChan2 chan<- int

    /*//將雙向管道賦給只讀的單向管道
    myChan1 = myChan
    fmt.Println(myChan1)

    //將雙向管道賦給只寫的單向管道
    myChan2 = myChan
    fmt.Println(myChan2)*/

    //將單向管道賦給雙向管道
    //會報錯
    //myChan = myChan1
    //fmt.Println(myChan)
}
  • 單向管道作為函數(shù)參數(shù)
package main

import (
    "fmt"
    "math/rand"
    "time"
)

//定義一個生產(chǎn)者
func producter(buff chan <- int)  {
    //定義隨機因子
    rand.Seed(time.Now().UnixNano())
    //生成隨機數(shù)
    for i := 0;i < 5 ;i++  {
        num := rand.Intn(100)
        //將產(chǎn)生的隨機數(shù)存儲到管道中
        buff <- num
        fmt.Println("生產(chǎn)者生產(chǎn)了", num)
    }
}

//定義消費者函數(shù)
func consumer(buff <- chan  int, exitChan chan <- int )  {
    for i := 0;i < 5 ;i++  {
        //讀取管道中的數(shù)據(jù)
        num := <-buff
        fmt.Println("------消費者消費", num)
    }
    //利用管道阻塞解決死循環(huán)問題
    exitChan<- 666
}
func main() {
    //定義兩個雙向管道
    myChan := make(chan int, 5)
    exitChan := make(chan int)

    //開啟兩個go程
    go producter(myChan)
    go consumer(myChan,exitChan)

    <-exitChan
}

select選擇結(jié)構(gòu)
  • 在企業(yè)開發(fā)中, 一般情況下使用select都是用于同時消費多個管道中數(shù)據(jù)
  • 在企業(yè)開發(fā)中, 一般情況下select中的default不用寫
  • 在企業(yè)開發(fā)中, 一般情況下使用select來控制退出主線程
  • 在企業(yè)開發(fā)中, 一般情況下使用select來處理超時
package main

import (
    "fmt"
    "time"
)

func main() {
    // 1.創(chuàng)建一個管道
    myCh1 := make(chan int, 5)
    //myCh2 := make(chan int, 5)
    //exitCh := make(chan bool)

    // 2.開啟一個協(xié)程生產(chǎn)數(shù)據(jù)
    go func() {
        time.Sleep(time.Second * 5)
        for i := 0; i < 10 ; i++ {
            myCh1<-i
            fmt.Println("生產(chǎn)者1生產(chǎn)了", i)
        }
        close(myCh1)
        //exitCh<-true
    }()
    /*
   go func() {
       time.Sleep(time.Second * 5)
       for i := 0; i < 10 ; i++ {
           myCh2<-i
           fmt.Println("生產(chǎn)者2生產(chǎn)了", i)
       }
       close(myCh2)
   }()

    // 2.在主線程中消費數(shù)據(jù)
   //for i := 0; i < 10 ; i++ {
   //   num := <-myCh
   //   fmt.Println("------消費者消費了", num)
   //}

   //for num := range myCh{
   //   fmt.Println("------消費者消費了", num)
   //}
    */
    // 注意點: 在企業(yè)開發(fā)中, 一般情況下使用select都是用于同時消費多個管道中數(shù)據(jù)
    //         在企業(yè)開發(fā)中, 一般情況下select中的default不用寫
    //         在企業(yè)開發(fā)中, 一般情況下使用select來控制退出主線程
    //         在企業(yè)開發(fā)中, 一般情況下使用select來處理超時
    for{
        //fmt.Println("start")
        select {
        case num1 := <-myCh1:
            fmt.Println("------消費者消費了myCh1", num1)
            //case num2 := <-myCh2:
            //  fmt.Println("------消費者消費了myCh2", num2)
            //case <-exitCh:
            //  return
        case <-time.After(3):
            fmt.Println("超時了")
            return
            //default:
            //  fmt.Println("生產(chǎn)者還沒有生產(chǎn)好數(shù)據(jù)")
        }
        //fmt.Println("=====================")
        time.Sleep(time.Millisecond)
    }
    fmt.Println("程序結(jié)束了")
}

管道是地址傳遞
package main

import "fmt"

func main() {
    //定義一個雙向有緩沖區(qū)管道
    var myChan = make(chan int,3)
    fmt.Println(myChan) //0xc00007c080
    fmt.Printf("%p\n", myChan) //0xc00007c080
    fmt.Printf("%p\n", &myChan)  //0xc000072018

    //管道是地址傳遞
    //定義一個單向管道
    var myChan2 chan <- int
    myChan2 = myChan
    //打印單向管道len和cap
    fmt.Println("len = ", len(myChan2),"cap = ", cap(myChan2))
    //打印雙向管道len和cap
    fmt.Println("len = ", len(myChan),"cap = ", cap(myChan))
}

定時器
  • 對時間的操作方法,一般都在time包中查找
package main

import (
    "fmt"
    "time"
)

func main() {
    /*
    type Timer struct {
        C <-chan Time
        r runtimeTimer
    }
    */

    //1.使用定時器,就要用到time包
    // NewTimer作用, 就是讓系統(tǒng)在指定時間之后, 往Timer結(jié)構(gòu)體的C屬性中寫入當前的時間
    // 讓程序阻塞3秒, 3秒之后再執(zhí)行
    //func NewTimer(d Duration) *Timer

    /*start := time.Now()
    fmt.Println(start)
    //使用定時器
    timer := time.NewTimer(time.Second * 3)
    fmt.Println(<-timer.C)*/

    //2.func After(d Duration) <-chan Time
    //這個定時器底層就是NewTimer實現(xiàn)的
    /*start := time.Now()
    fmt.Println(start)
    //使用定時器
    timer := time.After(time.Second * 3)
    fmt.Println(<-timer)*/

    // 以上的定時器都是一次性的定時器, 也就是只會執(zhí)行一次
    /*go func() {
        start := time.Now()
        fmt.Println(start)
        timer := time.After(time.Second * 3)
        for  {
            fmt.Println(<-timer)
        }
    }()

    for  {
        ;
    }*/

    //周期性定時器
    start := time.Now()
    fmt.Println(start)
    //定義周期性定時器
    timer := time.NewTicker(time.Second * 2)
    for {
        fmt.Println(<-timer.C)
        timer.Stop()
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 多線程同時執(zhí)行叫做并行 并發(fā)就是在不同線程中來回切換執(zhí)行來達到并行的效果就是并發(fā) 通過go可以在當前線程中開啟一個...
    AuglyXu閱讀 6,944評論 0 9
  • 多線程同步問題 互斥鎖互斥鎖的本質(zhì)是當一個goroutine訪問的時候, 其它goroutine都不能訪問這樣就能...
    極客江南閱讀 1,409評論 0 4
  • 系統(tǒng)文件介紹 在程序啟動運行時,自動打開,運行結(jié)束,自動關(guān)閉。 鍵盤(硬件)—— 標準輸入(文件)stdin —...
    泡泡龍吐泡泡閱讀 5,117評論 0 2
  • Go語言并發(fā)模型 Go 語言中使用了CSP模型來進行線程通信,準確說,是輕量級線程goroutine之間的通信。C...
    副班長國偉閱讀 2,194評論 0 2
  • 本文主要說明Redis事務(wù)功能的實現(xiàn)。 建議閱讀: 1、Redis事務(wù)的理論介紹見:Redis之事務(wù)實現(xiàn) I、上...
    wenmingxing閱讀 337評論 0 2

友情鏈接更多精彩內(nèi)容