6.併發

1. goroutine

go關鍵字實現的最簡單的併發, 注意程序會在main函數結束時退出程序,並不會等待其他goroutin結束。

package main

import (
    "fmt"
    "time"
)

func Add(x, y int)  {
    z := x + y
    fmt.Println(z)
}

func main() {
    x := 1
    y := 1
    for i:=0; i<10 ;i++  {
        go Add(x, y)
    }

    time.Sleep(4)
}

2.併發通信

併發通信通過 共享數據 和 消息來實現

package main
import (
    "fmt"
    "runtime"
    "sync"
)
var counter = 0
func Add(lock *sync.Mutex)  {
    lock.Lock()
    counter += 1
    lock.Unlock()
}
func main() {
    lock := &sync.Mutex{}
    for i:=0 ; i<10 ; i++  {
        go Add(lock)
    }
    for {
        lock.Lock()
        c := counter
        lock.Unlock()
        runtime.Gosched()   // 讓出cpu的時間片,程序遇到goshed就好讓出cpu讓給其他goroutine執行
        if c >= 10{
            break
        }
    }
    fmt.Println(counter)
}

3. chanel

package main

import "fmt"

func Count(count *int, ch chan int)  {
    *count = *count + 1
    fmt.Println(*count)
    ch <- 1    // 想channel 中寫入數據,在channel中的數據被讀取之前,這個操作是阻塞的
}

func main() {
    count := 0
    chs := make([]chan int, 5)
    for i:=0;i<5;i++{
        chs[i] = make(chan int)
        go Count(&count,chs[i])
    }
    for _, ch := range chs{
        <- ch   // 從channel中讀取數據,在數據被寫入之前,這個操作是
    }
}

3.1 基本語法

// 聲明
var chanName chan ElementType
//在類型之前加了一個chan關鍵字

var ch chan int

var m map[string] chan bool

// 定義
ch = make(chan int)   // 初始化一個int型的channel


//channel 的讀寫
ch <- value // 將一個數據發送至channel, 寫入數據會產生阻塞,知道其他grountine從channel中將數據讀走
varlue = <-ch  // 從channel讀取數據,如果channel中沒數據也會導致阻塞,知道有數據寫入
package main

import "fmt"

func Counsumer(ch1, ch2 chan int)  {
    var value int
    for {
        value = <-ch1
        if value >= 100{
            ch2 <- 0
        }
        fmt.Println("消費了", value)
    }
}

func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)
    go Counsumer(ch1, ch2)
    var i int = 0
    for i < 100{
        i += 1
        ch1 <- i
        fmt.Println("生產了", i)
    }
    <- ch2
}

3.2 select

select 語法與switch相似,只不過每一個case條件必須是一個channel操作。

package main
import "fmt"
func main() {
    ch := make(chan int, 1)
    j := 0
    for j < 100{
        j += 1
        select {
        case ch <- 0:
        case ch <- 1:
        }
        i := <-ch
        fmt.Println(i)
    }
}

執行上面代碼會發現打印的數字有時爲1有時爲0。這與select的機制有關,它是隨機的。

3.3 緩衝機制

package main

import "fmt"

func Consumer(ch chan int)  {
    for{
        value := <-ch
        fmt.Println("消費了", value)
    }
}

func main() {
    ch := make(chan int, 100)   // 構造channel的時候可以指定channel的緩存大小
    i := 0                                 // 設置緩存大小後,如果chan沒滿則生產環節不再阻塞

    go Consumer(ch)

    for i < 100{
        i += 1
        print("生產", i, "\n")
        ch <- i
    }
}

帶緩衝區的channel還可以這樣讀

package main

import (
    "fmt"
)

func Consumer(ch, ch2 chan int)  {
    Loop1:
    for   {
        //value := <-ch
        for value := range ch{
            fmt.Println("消費了", value)
            if value >= 100{
                break Loop1
            }
        }
    }
    fmt.Println("lalala")
    ch2 <- 0
}

func main() {
    ch := make(chan int, 100)
    ch2 := make(chan int)
    i := 0
    go Consumer(ch, ch2)
    for i < 100{
        i += 1
        print("生產", i)
        ch <- i
    }
    <-ch2
}

3.4超時機制

package main

import (
    "fmt"
    "time"
)


func main() {
    timeout := make(chan int, 1)
    func() {
        time.Sleep(1)
        timeout <- 1
    }()
    ch := make(chan int, 1)
    ch <- 1
    t := time.Now()
    select{
    case <-ch:
        fmt.Println("從ch中讀到數據")
    case <-timeout:
        fmt.Println("從timeout讀取數據")
    }
    fmt.Println(time.Now().Sub(t))
}

 3.5 channel的傳遞                                                                                                                                                             

chanel本身也是原生類型,與map之類的類型一樣,channel定義之後也可以通過channel傳遞

package main

type Pipe struct {
    value int
    handler func(i int) int
    next chan int
}

func handle(queue chan *Pipe)  {
    for data := range queue{
        data.next <- data.handler(data.value)
    }    
}

func add(i int)int  {
    return i + 1
}

func main() {
    var p1 Pipe = Pipe{1, add, make(chan int,1)}
}

3.6 單向channel

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


ch4 := make(chan int)
ch5 := <-chan int(ch4)    // 將ch4轉換爲單向讀取channel
ch6 := chan<- int(ch4)    // 將ch4轉換爲單向寫channel


// 用法
func Parse(ch <-chan int) {
    for value := range ch{    // 在這個函數中只能對ch進行讀操作
        fmt.Println(value)
    }
}
package main

import (
    "fmt"
    "time"
)

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

func main() {
    ch4 := make(chan int, 1)
    go read(ch4)
    ch4 <- 1
    time.Sleep(1e9)
}

3.7 關閉channel

close(ch)

3.8 出讓時間片

runtime.Gosched()
// 如果要精細的控制goroutine的行爲,就必須深入的瞭解runtime包提供的具體功能

3.9 同步

3.9.1同步鎖:

  sync.Mutex 

  sync.RWmutex  單寫多讀模型,一個goroutine獲取讀鎖之後,只限制寫,其他goroutine仍可以讀。而寫鎖會組織所有goroutine獲取讀鎖和寫鎖

  Lock 寫鎖

  RLock 讀鎖

var l sync.Mutex
func foo() {
    l.Lock()
    defer l.Unlock()  
}

3.9.2全局唯一性操作

先來看一段代碼

package main

import "fmt"

var done bool = false

func setup()  {
    fmt.Println("hello, world")
    done = true
}

func doprint() {
    if !done{
        setup()
    }
}

這段代碼的目的是保證setup函數只被執行一次。但是細看還是會有問題,因爲setup並不是一個原子性操作, 這種寫法可能會導致setup函數被多次調用。

 

    

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章