Golang - 併發編程

Golang - 併發編程

1. 並行和併發

  • 並行:在同一時刻,有多條指令在多個CPU處理器上同時執行
  • 2個隊伍,2個窗口,要求硬件支持
  • 併發:在同一時刻,只能有一條指令執行,但多個進程指令被快速地輪換執行
  • 2個隊伍,1個窗口,要求提高軟件能力

2. go語言併發優點

  • go從語言層面就支持了併發
  • 簡化了併發程序的編寫

3. goroutine是什麼

  • 它是go併發設計的核心
  • goroutine就是協程,它比線程更小,十幾個goroutine在底層可能就是五六個線程
  • go語言內部實現了goroutine的內存共享,執行goroutine只需極少的棧內存(大概是4~5KB)

4. 建立goroutine

  • 只須要在語句前添加go關鍵字,就能夠建立併發執行單元編程

    package main緩存

    import (
    "fmt"
    "time"
    )安全

    //測試協程
    //循環打印內容
    func newTask() {
    i := 0
    for {
    i++
    fmt.Printf("new goroutine:i=%d\n", i)
    time.Sleep(1 * time.Second)
    }
    }併發

    //main()至關因而主協程
    func main() {
    //啓動子協程
    go newTask()
    i := 0
    for {
    i++
    fmt.Printf("main goroutine:i=%d\n", i)
    time.Sleep(1 * time.Second)
    }
    }函數

  • 開發⼈員無需瞭解任何執⾏細節,調度器會自動將其安排到合適的系統線程上執行
  • 若是主協程退出了,其餘任務還執行嗎?不執行測試

    package main
    
      import (
         "fmt"
         "time"
      )
    
      //main()至關因而主協程
      func main() {
         //匿名子協程
         go func() {
            i := 0
            for {
               i++
               fmt.Println("子協程 i=", i)
               time.Sleep(1 * time.Second)
            }
         }()
         i := 0
         for {
            i++
            fmt.Println("主協程 i=", i)
            time.Sleep(1 * time.Second)
            //主協程第二次後退出
            if i == 2 {
               break
            }
         }
      }
  • 程序沒任何輸出,也不報錯線程

    package main
    
      import (
         "fmt"
         "time"
      )
    
      //main()至關因而主協程
      func main() {
         //匿名子協程
         go func() {
            i := 0
            for {
               i++
               fmt.Println("子協程 i=", i)
               time.Sleep(1 * time.Second)
            }
         }()
      }

5. runtime包

  • runtime.Gosched():用於讓出CPU時間片,調度器從新安排任務調度,仍是有概率分配到它的設計

    package main
    
      import (
         "fmt"
         "runtime"
      )
    
      func main() {
         //匿名子協程
         go func(s string) {
            for i := 0; i < 2; i++ {
               fmt.Println(s)
            }
         }("world")
         //主協程
         for i := 0; i < 2; i++ {
            runtime.Gosched()
            fmt.Println("hello")
         }
      }
  • runtime.Goexit():當即終止當前協程code

    package main
    
      import (
         "fmt"
         "time"
         "runtime"
      )
    
      func main() {
         //匿名子協程
         go func() {
            defer fmt.Println("A.defer")
            //匿名函數
            func() {
               defer fmt.Println("B.defer")
               //此時只有defer執行
               runtime.Goexit()
               fmt.Println("B")
            }()
            fmt.Println("A")
         }()
         for {
            time.Sleep(time.Second)
         }
      }
  • runtime.GOMAXPROCS():設置並行計算的CPU核數,返回以前的值

    package main
    
      import (
         "runtime"
         "fmt"
      )
    
      func main() {
         n := runtime.GOMAXPROCS(3)
         fmt.Println("n=%d\n",n)
         //循環執行2個
         for{
            go fmt.Print(0)
            fmt.Print(1)
         }
      }

6. channel是什麼

  • goroutine運行在相同的地址空間,所以訪問共享內存必須作好同步,處理好線程安全問題
  • goroutine奉行經過通訊來共享內存,而不是共享內存來通訊
  • channel是一個引用類型,用於多個goroutine通信,其內部實現了同步,確保併發安全

7. channel的基本使用

  • channel能夠用內置make()函數建立

  • 定義一個channel時,也須要定義發送到channel的值的類型

    make(chan 類型)   //無緩衝的通道
      make(chan 類型, 容量) //有緩衝的通道
  • 當 capacity= 0 時,channel 是無緩衝阻塞讀寫的,當capacity> 0 時,channel 有緩衝、是非阻塞的,直到寫滿 capacity個元素才阻塞寫入

  • channel經過操做符<-來接收和發送數據,發送和接收數據語法:

    channel <- value   //發送value到channel
      <-channel          //接收通道數據,並丟棄
      x := <-channel    //通道取值並賦給x
      x, ok := <-channel //ok是檢查通道是否關閉或者是否爲空
  • channel基本使用

    package main
    
      import "fmt"
    
      func main() {
         //建立存放int類型的通道
         c := make(chan int)
         //子協程
         go func() {
            defer fmt.Println("子協程結束")
            fmt.Println("子協程正在運行...")
            //將666發送到通道c
            c <- 666
         }()
         //若已取出數據,下面再取會報錯
         //<-c
         //主協程取數據
         //從c中取數據
         num := <-c
         fmt.Println("num = ", num)
         fmt.Println("主協程結束")
      }

8. 無緩衝的channel

  • 無緩衝的通道是指在接收前沒有能力保存任何值的通道
  • 無緩衝通道,有可能阻塞

發送者 -> (通道(有可能有數據阻塞)) -> 接受者

package main

import (
   "fmt"
   "time"
)

func main() {
   //建立無緩衝通道
   c := make(chan int, 0)
   //長度和容量
   fmt.Printf("len(c)=%d, cap(c)=%d\n", len(c), cap(c))
   //子協程存數據
   go func() {
      defer fmt.Println("子協程結束")
      //向通道添加數據
      for i := 0; i < 3; i++ {
         c <- i
         fmt.Printf("子協程正在運行[%d]:len(c)=%d,cap(c)=%d\n", i, len(c), cap(c))
      }
   }()

   time.Sleep(2 * time.Second)
   //主協程取數據
   for i := 0; i < 3; i++ {
      num := <-c
      fmt.Println("num=", num)
   }
   fmt.Println("主協程結束")
}

9. 有緩衝的channel

  • 有緩衝的通道是一種在被接收前能存儲一個或者多個值的通道

發送者 -> (通道(數據),(數據)(...)) -> 接受者

  • 上面代碼建立時修改容量便可

    //建立有緩存的通道
      c :=make(chan int, 3)

10. close()

  • 能夠經過內置的close()函數關閉channel

    package main
    
      import "fmt"
    
      func main() {
         //建立通道
         c := make(chan int)
         //子協程存數據
         go func() {
            for i := 0; i < 5; i++ {
               c <- i
            }
            //子協程close
            close(c)
         }()
         //主協程取數據
         for {
            if data, ok := <-c; ok {
               fmt.Println(data)
            } else {
               break
            }
         }
         fmt.Println("Finshed")
      }
  • 也能夠以下遍歷

    for data := range c{
          fnt.Println(data)
      }

11. 單方向的channel

  • 默認狀況下,通道是雙向的,也就是,既能夠往裏面發送數據也能夠接收數據

  • go能夠定義單方向的通道,也就是隻發送數據或者只接收數據,聲明以下

    var ch1 chan int //正常的
    var ch2 chan<- float64 //單向的,只用於寫float64的數據
    var ch3 <-chan int //單向的,只用於讀取int數據

  • 能夠將 channel 隱式轉換爲單向隊列,只收或只發,不能將單向 channel 轉換爲普通channel

func main() {
//建立通道
c := make(chan int, 3)

//1. 將c準換爲只寫的通道
var send <- chan int =c

//2. 將c轉爲只讀的通道
var recv <- chan int =c

//往send裏面寫數據
send < -1

//從recv讀數據
<-recv

}

  • 單方向的channel有什麼用?模擬生產者和消費者

    package main
    
      import "fmt"
    
      //生產者,只寫
      func producter(out chan<- int) {
         //關閉資源
         defer close(out)
         for i := 0; i < 5; i++ {
            out <- i
         }
      }
    
      //消費者,只讀
      func consumer(in <-chan int) {
         for num := range in {
            fmt.Println(num)
         }
      }
    
      func main() {
         //建立通道
         c := make(chan int)
         //生產者運行,向管道c存數據
         go producter(c)
         //消費者運行
         consumer(c)
         fmt.Println("done")
      }

12. 定時器

  • Timer:定時,時間到了響應一次

    package main
    
      import (
         "time"
         "fmt"
      )
    
      func main() {
         //1.基本使用
         //建立定時器
         //2秒後,定時器會將一個時間類型值,保存向本身的c
         //timer1 := time.NewTimer(2 * time.Second)
         ////打印當前時間
         //t1 := time.Now()
         //fmt.Printf("t1:%v\n", t1)
         ////從管道中取出C打印
         //t2 := <-timer1.C
         //fmt.Printf("t2:%v\n", t2)
    
         //2.Timer只響應一次
         //timer2 := time.NewTimer(time.Second)
         //for {
         // <-timer2.C
         // fmt.Println("時間到")
         //}
    
         //3.經過Timer實現延時的功能
         ////(1)睡眠
         //time.Sleep(2*time.Second)
         //fmt.Println("2秒時間到")
         ////(2)經過定時器
         //timer3 := time.NewTimer(2 * time.Second)
         //<-timer3.C
         //fmt.Println("2秒時間到")
         ////(3)After()
         //<-time.After(2 * time.Second)
         //fmt.Println("2秒時間到")
    
         //4.中止定時器
         //timer4 := time.NewTimer(3 * time.Second)
         ////子協程
         //go func() {
         // <-timer4.C
         // fmt.Println("定時器器時間到,能夠打印了")
         //}()
         //stop := timer4.Stop()
         //if stop {
         // fmt.Println("timer4已關閉")
         //}
    
         //5.重置定時器
         timer5 := time.NewTimer(3 * time.Second)
         //定時器改成1秒
         timer5.Reset(1 * time.Second)
         fmt.Println(time.Now())
         fmt.Println(<-timer5.C)
    
         for {
         }
      }
  • Ticker:響應屢次

    package main

    import (
    "time"
    "fmt"
    )

    func main() {
    //建立定時器,間隔1秒
    ticker := time.NewTicker(time.Second)

    i := 0
     //子協程
     go func() {
        for {
           <-ticker.C
           fmt.Println(<-ticker.C)
           i++
           fmt.Println("i=", i)
           //中止定時器
           if i == 5 {
              ticker.Stop()
           }
        }
     }()
    
     //死循環
     for {
    
     }

    }

13. select

  • go語言提供了select關鍵字,能夠監聽channel上的數據流動
  • 語法與switch相似,區別是select要求每一個case語句裏必須是一個IO操做

    select {
      case <-chan1:
         // 若是chan1成功讀到數據,則進行該case處理語句
      case chan2 <- 1:
         // 若是成功向chan2寫入數據,則進行該case處理語句
      default:
         // 若是上面都沒有成功,則進入default處理流程
      }
    
      package main
    
      import (
         "fmt"
      )
    
      func main() {
         //建立數據通道
         int_chan := make(chan int, 1)
         string_chan := make(chan string, 1)
         //建立2個子協程,寫數據
         go func() {
            //time.Sleep(2 * time.Second)
            int_chan <- 1
         }()
         go func() {
            string_chan <- "hello"
         }()
         //若是都能匹配到,則隨機選擇一個去跑
         select {
         case value := <-int_chan:
            fmt.Println("intValue:", value)
         case value := <-string_chan:
            fmt.Println("strValue:", value)
         }
         fmt.Println("finish")
      }

14. 攜程同步鎖

  • go中channel實現了同步,確保併發安全,同時也提供了鎖的操做方式
  • go中sync包提供了鎖相關的支持
  • Mutex:以加鎖方式解決併發安全問題

    package main
    
      import (
         "time"
         "fmt"
         "sync"
      )
    
      //帳戶
      type Account struct {
         money int
         flag sync.Mutex
      }
    
      //模擬銀行檢測
      func (a *Account)Check()  {
         time.Sleep(time.Second)
      }
    
      //設置帳戶餘額
      func (a *Account)SetAccount(n int)  {
         a.money +=n
      }
    
      //查詢帳戶餘額
      func (a *Account)GetAccount() int{
         return a.money
      }
    
      //買東西1
      func (a *Account)Buy1(n int){
         a.flag.Lock()
         if a.money>n{
            //銀行檢測
            a.Check()
            a.money -=n
         }
         a.flag.Unlock()
      }
    
      //買東西2
      func (a *Account)Buy2(n int){
         a.flag.Lock()
         if a.money>n{
            //銀行檢測
            a.Check()
            a.money -=n
         }
         a.flag.Unlock()
      }
    
      func main() {
         var account Account
         //設置帳戶餘額
         account.SetAccount(10)
         //2個子協程買東西
         go account.Buy1(6)
         go account.Buy2(5)
         time.Sleep(2 * time.Second)
         fmt.Println(account.GetAccount())
      }
  • sync.WaitGroup:用來等待一組子協程的結束,須要設置等待的個數,每一個子協程結束後要調用Done(),最後在主協程中Wait()便可
  • 引入

    package main
    
      import (
         "fmt"
      )
    
      func main() {
         //建立通道
         ch := make(chan int)
         //count表示活動的協程個數
         count := 2
         go func() {
            fmt.Println("子協程1")
            //子協程1執行完成,給通道發送信號
            ch <-1
         }()
         go func() {
            fmt.Println("子協程2")
            ch <-1
         }()
         //time.Sleep(time.Second)
         //從ch中不斷讀數據
         for range ch{
            count --
            if count == 0{
               close(ch)
            }
         }
      }
  • go提供了這種解決方案sync.WaitGroup
  • Add():添加計數
  • Done():操做結束時調用,計數減去1
  • Wait():主函數調用,等待全部操做結束


未完待續...

相關文章
相關標籤/搜索