二十三、管道
在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())
}
上一篇: 【Go编程学习】Go初探
下一篇: Go的类型变量和函数