golang中的并发是函数相互独立运行的能力。Goroutines是并发运行的函数。Golang提供了
- 如何实现go协程
只需要在函数前面加上go即可
go task()
package main
import (
"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 main
import (
"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 main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func 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 main
import (
"fmt"
"runtime"
"sync"
"time"
)
var wg sync.WaitGroup
var value int = 100
var lock sync.Mutex
func a() {
defer wg.Done()
for i := 0; i < 100; i++ {
lock.Lock()
value += 1
lock.Unlock()
fmt.Println("a++")
time.Sleep(time.Microsecond * 100)
}
}
func b() {
defer wg.Done()
for i := 0; i < 100; i++ {
lock.Lock()
value -= 1
lock.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 main
import (
"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 main
import (
"fmt"
"time"
)
var valueint = make(chan int)
var valurstring = make(chan string)
func main() {
//关闭通道后会读取类型默认值,如int默认值为0
go func() {
valueint <- 100
valurstring <- "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 main
import (
"fmt"
"runtime"
"time"
)
var valueint = make(chan int)
func main() {
ticker := time.NewTicker(time.Second)
//关闭通道后会读取类型默认值,如int默认值为0
go func() {
//每秒钟随机的往通道插入数据
for _ = range ticker.C {
select {
case valueint <- 1:
case valueint <- 2:
case valueint <- 3:
}
}
}()
var sum int
for v := range valueint {
fmt.Printf("v: %v\n", v)
sum += v
if sum >= 10 {
runtime.Goexit()
}
}
}