golang中的并发是函数相互独立运行的能力。Goroutines是并发运行的函数。Golang提供了
- 如何实现go协程
只需要在函数前面加上go即可
go task()
package mainimport ("fmt""time"
)func show(msg string) {for i := 0; i < 5; i++ {fmt.Printf("msg: %v\n", msg)time.Sleep(time.Microsecond * 1000)}
}func main() {go show("go show") //开辟goroutines中运行show("haha") //这个会在main函数的goroutines中运行//注意协程是主进程的守护进程,主进程结束,协程就结束了,不管运没运行完fmt.Println("end..........")
}
- channel
它是一个数据管道,可以往里面写数据,从里面读数据。
channel 是 goroutine 之间数据通信桥梁,而且是线程安全的。
channel 遵循先进先出原则。
写入,读出数据都会加锁。
channel 可以分为 3 种类型:
- 只读 channel,单向 channel
- 只写 channel,单向 channel
- 可读可写 channel
channel 还可按是否带有缓冲区分为:
- 带缓冲区的 channel,定义了缓冲区大小,可以存储多个数据
- 不带缓冲区的 channel,只能存一个数据,并且只有当该数据被取出才能存下一个数据
unbuffered:=make(channel int)
buffered:=make(channel int,10)
package mainimport ("fmt""math/rand""time"
)var channeltest = make(chan int)func show(msg string) {value := rand.Intn(10)channeltest <- value // 往管道中写数据}func main() {defer close(channeltest) // 关闭管道go show("go show") //开辟goroutines中运行time.Sleep(time.Second * 1)value := <-channeltest //从channel中获取数据fmt.Printf("value: %v\n", value)//注意协程是主进程的守护进程,主进程结束,协程就结束了,不管运没运行完fmt.Println("end..........")
}
- waitgroup实现同步
类似于.net中的task wait,你可以取消下面wg 的试试看两者的区别,我们添加完wg后发现,a以及b函数都执行完毕
package mainimport ("fmt""sync""time"
)var wg sync.WaitGroupfunc a() {defer wg.Done()for i := 0; i < 10; i++ {fmt.Println("a函数")time.Sleep(time.Microsecond * 100)}
}func b() {defer wg.Done() //别忘了在一次执行完毕后运行这个函数for i := 0; i < 10; i++ {fmt.Println("b函数")time.Sleep(time.Microsecond * 100)}
}func main() {wg.Add(1) //在每一个go之前执行这个行数go a() //开辟goroutines中运行wg.Add(1)go b()wg.Wait()//注意协程是主进程的守护进程,主进程结束,协程就结束了,不管运没运行完fmt.Println("end..........")
}
- Mutex
锁,用来解决同时访问全局变量
package mainimport ("fmt""runtime""sync""time"
)var wg sync.WaitGroup
var value int = 100
var lock sync.Mutexfunc a() {defer wg.Done()for i := 0; i < 100; i++ {lock.Lock()value += 1lock.Unlock()fmt.Println("a++")time.Sleep(time.Microsecond * 100)}
}func b() {defer wg.Done()for i := 0; i < 100; i++ {lock.Lock()value -= 1lock.Unlock()fmt.Println("a--")time.Sleep(time.Microsecond * 100)}
}func main() {fmt.Printf("runtime.NumCPU(): %v\n", runtime.NumCPU())runtime.GOMAXPROCS(8) //最大并发wg.Add(1)go a()wg.Add(1) //开辟goroutines中运行go b()wg.Wait()fmt.Println("end..........")fmt.Printf("value: %v\n", value)
}
- runtime包
- runtime.Gosched() 让出CPU,重新等待安排任务
package mainimport ("fmt""runtime"
)func a() {for i := 0; i < 2; i++ {fmt.Println("a函数")}}func main() {fmt.Printf("runtime.NumCPU(): %v\n", runtime.NumCPU())runtime.GOMAXPROCS(8) //最大并发go a() //开辟goroutines中运行for i := 0; i < 2; i++ {runtime.Gosched() //让出CPU,执行A函数fmt.Printf("i: %v\n", i)}fmt.Println("end..........")
}
- runtime.Goexit() 退出协程
- runtime.GOMAXPROCS(8) 最大并发
- select Switch
- select是Go中的一个控制结构,类似于Switch语句,用于处理异步IO操作。select会监听语句中channel的读写操作,当case中channel读写操作为非阻塞状态时,将会触发相应的动作
- select中的case语句必须是一个channel操作
- select中的default自居总是可运行的
- 如果有多个case都可以运行,select会随机公平的选出一个执行,其他不会执行
- 如果没有可执行的case语句,且有default语句,那么会执行default
- 如果没有可运行的case语句,且没有default语句,select将阻塞,知道某个case可以运行
package mainimport ("fmt""time"
)var valueint = make(chan int)
var valurstring = make(chan string)func main() {//关闭通道后会读取类型默认值,如int默认值为0go func() {valueint <- 100valurstring <- "haha"defer close(valueint)defer close(valurstring)}()for {select {case r := <-valueint:fmt.Printf("valueint: %v\n", r)case r := <-valurstring:fmt.Printf("valurstring: %v\n", r)default:fmt.Println("default .....")}time.Sleep(time.Second)}
}
- ticker
ticker会一直运行,timer只运行一次
package mainimport ("fmt""runtime""time"
)var valueint = make(chan int)func main() {ticker := time.NewTicker(time.Second)//关闭通道后会读取类型默认值,如int默认值为0go func() {//每秒钟随机的往通道插入数据for _ = range ticker.C {select {case valueint <- 1:case valueint <- 2:case valueint <- 3:}}}()var sum intfor v := range valueint {fmt.Printf("v: %v\n", v)sum += vif sum >= 10 {runtime.Goexit()}}
}