二十三、管道

在go語言中可以通過chan來定義管道,可以通過操作符<-和->對管道進行讀取和寫入操作
通過管道維護例程狀態:

1.讀寫管道(聲明&賦值&操作)

使用make函數初始化,make(chan type)/make(chan type, len),不帶len參數的用於創建無緩存區的管道,使用len創建指定緩衝區長度的管道
讀寫管道
可通過操作符<-和->對管道進行讀取和寫入操作,當寫入無緩衝區管道或由緩衝區管道已滿時寫入則會阻塞直到管道中元素被其他例程讀取。同理,當管道中無元素時讀取時也會阻塞到管道被其他例程寫入元素

package main

import "fmt"

//管道

func main() {
   //管道初始化必須用make
   var notice chan string = make(chan string)
   fmt.Printf("%v %T\n",notice,notice)

   go func() {
      fmt.Println("start")
      notice <- "xxx"
   }()
   fmt.Println("open")
   fmt.Println( <- notice)
   fmt.Println("close")


}
package main

import (
   "fmt"
   "runtime"
   "time"
)

func PrintCharts(name int,channel chan int){
   for i:= 'A';i <= 'Z';i++{
      fmt.Printf("%v %c\n",name,i)
      runtime.Gosched()//讓出cpu
   }
   channel <- name
   fmt.Println("寫入",name)

}

func main() {
   var notice chan int = make(chan int)
   for i:=0;i<10;i++{
      go PrintCharts(i,notice)
   }

   for i:=0;i<10;i++{
      fmt.Println("讀取",<-notice)
   }

   fmt.Println(" over")
   time.Sleep(time.Second *10)
}

2.只讀只寫管道(聲明&賦值&操作)

管道是聲明需要指定管道存放數據的類型,管道原則可以存放任何類型,但只建議用於存放值類型或者只包含值類型的結構體。在管道聲明後,會被初始化爲nil
只讀/只寫管道
可以在函數參數時聲明管道爲chan<-或chan->,表示管道只寫或只讀

package main

import (
   "fmt"
   "time"
)

func main() {
   var channel chan int = make(chan int,5)
   var rchannel <-chan int = channel   //只讀管道
   var wchannel chan<- int = channel   //只寫管道
   //雖然變量不同但是底層用的一個管道

   go func(a <-chan int) {
      fmt.Println(<-a)
   }(rchannel)

   go func(a chan<- int) {
      a<- 1
   }(wchannel)
   wchannel<- 2
   fmt.Println(<-rchannel)
   time.Sleep(time.Second*3)

}

3.帶緩衝管道&關閉管道

關閉管道
可通過close函數關閉管道,關閉後的管道不能被寫入,當讀取到最後一個元素後可通過讀取的第二個參數用於判斷是否結束
遍歷管道
可以通過for-range進行遍歷

package main

import "fmt"

func main() {
   var channel chan string = make(chan string,2)
   //2是寫入次數
   fmt.Println(len(channel))
   channel <- "ab"
   fmt.Println(len(channel))
   channel <- "b"
   fmt.Println(len(channel))
   fmt.Println(<-channel)
   fmt.Println(len(channel))
   fmt.Println(<-channel)
   fmt.Println(len(channel))

   channel <- "c"
   channel <- "cb"
   //關閉管道 關閉之後不能再寫入 只能讀取
   close(channel)
   //管道也可以用range遍歷  當然你要考慮管道的寫入次數問題
   //如果不關閉管道直接遍歷會出現死鎖
   for ch := range channel{
      fmt.Println(ch)
   }


}

4.多路複用

select-case
當寫入無緩衝區管道或由緩衝區管道已滿時寫入則會阻塞直到管道中元素被其他例程讀取。同理,當管道中無元素時讀取時也會阻塞到管道被其他例程寫入元素,若需要同時對多個管道進行監聽(寫入或讀取),則可以使用select-case語句

package main

import "fmt"

func main() {
   var channel01 chan int = make(chan int,5)
   var channel02 chan int = make(chan int,5)

   go func() {
      channel01<- 1
   }()

   go func() {
      channel02<- 2
   }()

   select {
   //每當有一個讀成功 就走對應的case
   case <- channel01:
      fmt.Println("01")
   case <- channel02:
      fmt.Println("02")



   }

}
package main

import "fmt"

func main() {
   var channel01 chan int = make(chan int,5)
   var channel02 chan int = make(chan int,5)
   channel01 <- 1
   channel02 <- 2
   go func() {
      <-channel01
   }()

   go func() {
      <-channel02
   }()

   select {
   //每當有一個寫成功 就走對應的case
   case channel01<-1:
      fmt.Println("01")
   case channel02<-1:
      fmt.Println("02")



   }

}

5.超時機制

超時機制-可以通過select-case實現對執行操作超時的控制
Select-case語句監聽每個case語句中管道的讀取,當某個case語句中管道讀取成功則執行對應子語句
Go語言time包實現了After函數,可以用於實現超時機制,After函數返回一個只讀管道
select-case語句

package main

import (
   "fmt"
   "time"
)

func main() {
   fmt.Println(time.Now())
   fmt.Println("after")
   channel := time.After(3 * time.Second)//表示多久後執行
   fmt.Println(<-channel)

   ticker:= time.Tick(3 * time.Second)//表示間隔多久執行一次
   fmt.Println("ticker")
   fmt.Println(<-ticker)
   for now := range ticker{
      fmt.Println(now)
   }

}
package main

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

func main() {
   rand.Seed(time.Now().Unix())
   result := make(chan int)
   //timeout := make(chan int)
   go func() {
      time.Sleep(time.Duration(rand.Intn(10))*time.Second)
      result <- 0
   }()

   //go func() {
   // time.Sleep(time.Second *3)
   // timeout <- 0
   //}()
   select {
   //可以用這種方式來判斷工作例程是否超時
   case <- result:
      fmt.Println("執行完成")
      //time.after 可以傳遞一個時間  然後規定時間後 會寫入管道一個數據 可以用來判斷延時
   case <-time.After(4*time.Second):
      fmt.Println("執行超時")
   }

}

6.常用包(sync)

sync包提供了同步原語,常用結構體有:
sync.Mutex:互斥鎖
sync.RWMutex:讀寫鎖
sync.Cond:條件等待
sync.Once:單次執行
sync.Map:例程安全映射
sync.Pool:對象池
sync.WaitGroup:組等待

(1)sync.Map:例程安全映射

package main

import (
   "fmt"
   "sync"
)

func main() {
   var users sync.Map//對映射的增刪改查是線程安全 就是查的時候不可以刪改 會有鎖
   users.Store(10,"aa")
   users.Store(20,"bb")  //存入key對應的value
   if value,ok := users.Load(10);ok{   //load讀取對應key的value  會有一個ok 如果存在是true 不存在則爲false
      fmt.Println(value.(string))  //返回的value是一個interface  需要做格式轉換
   }
   if value,ok:= users.Load(20);ok{
      fmt.Println(value.(string))
   }
   users.Delete(10)  //刪除這個key  value也就沒了
   if value,ok := users.Load(10);ok{
      fmt.Println(value)
   }
}

(2)sync.Once:單次執行

package main

import (
   "fmt"
   "sync"
)

func main() {
   var once sync.Once  //once 表示函數執行一次
   for i:=0;i<=10;i++{
      once.Do(func(){   //這裏是調用函數就不需要後面()傳參
         fmt.Println(i)
      })
   }
}

(3)sync.Pool:對象池

package main

import (
   "fmt"
   "sync"
)



func main() {
   //線程池
   pool := sync.Pool{
      New: func() interface{} {
      fmt.Println("new")
      return 1
   }}
   //在獲取的時候會創建
   x := pool.Get()
   fmt.Println(x)
   //獲取完之後將元素放回池
   pool.Put(x)
   //再次獲取 從池子中拿出來
   x = pool.Get()
   //繼續獲取就需要重新再創建一個
   x = pool.Get()

   print()
   fmt.Println()
}
package main

import (
   "fmt"
   "sync"
)
//New 是一個函數返回值是一個接口
//自定義類型
type New func() interface{}
type A interface {

}
type Pool struct {
   mutex sync.Mutex
   objects []interface{}
   new  New
}

func NewPool(size int,new New) *Pool{
   objects := make([]interface{},size)
   for i :=0;i<size;i++{
      objects[i]= new()
   }
   return &Pool{
      objects: objects,
      new:     new,
   }
}

func (p *Pool) Get() interface{} {
   p.mutex.Lock()
   defer p.mutex.Unlock()
   if len(p.objects)>0 {
      obj := p.objects[0]
      p.objects = p.objects[1:]
      return obj
   }else {
      return p.new
   }
}


func (p *Pool) Put(obj interface{}) {
   p.mutex.Lock()
   defer p.mutex.Unlock()
   p.objects = append(p.objects,obj)
}

func main() {
   pool := NewPool(2, func() interface{} {
      fmt.Println("new")
      return  1
   })

   x := pool.Get()
   fmt.Println(x)
   pool.Put(x)
   x = pool.Get()
   x = pool.Get()

   print()
   fmt.Println()
}

7.Runtime

runtime包提供了與Go運行時系統交互的操作,常用函數:
runtime.Gosched(): 當前goroutine讓出時間片
runtime.GOROOT(): 獲取Go安裝路徑
runtime.NumCPU(): 獲取可使用的邏輯CPU數量
runtime.GOMAXPROCS(1):設置當前進程可使用的邏輯CPU數量
runtime.NumGoroutine(): 獲取當前進程中goroutine的數量

package main

import (
   "fmt"
   "runtime"
)

func main() {
   //獲取go安裝路徑
   fmt.Println(runtime.GOROOT())
   //獲取cpu核心數
   fmt.Println(runtime.NumCPU())
   //調整cpu使用數量
   fmt.Println(runtime.GOMAXPROCS(1))
   //獲取例程數量
   fmt.Println(runtime.NumGoroutine())
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章