并发
在go中实现并发使用的是goroutine
,类似于线程,但是goroutine
是go的运行时runtime
调度和管理的,不需要自己去写线程、进程、协程这些玩意。
01 基本使用
- 1 函数
语法
go 函数()
注意:
一个goroutine
必定对应一个函数,可以创建多个goroutine
去执行相同的函数。
package main
import (
"fmt"
"time"
)
func main() {
go HelloWorld()
go HelloWorld()
fmt.Println("main") // 结果只打印了main
}
func HelloWorld() {
fmt.Println("hello world")
}
注意:
当main()
函数运行完毕后,会把该mian()
函数中启动的goroutine
全部停止
02 GOMAXPROCS
指定程序并发时占用的CPU核数,也就是使用多少线程来同时运行go的代码。
go1.5之前默认是单核执行,之后默认的是机器上的CPU核数。
- 单核时,一个运行完之后,另一个才开始运行
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(1)
go HelloWorld()
go a()
fmt.Println("main")
time.Sleep(time.Second)
}
func HelloWorld() {
for i := 1; i < 10; i++ {
fmt.Println(i, "hello world")
}
}
func a() {
for i := 1; i < 10; i++ {
fmt.Println(i, "a")
}
}
注意:
先运行a
函数,运行完在运行HelloWorld
函数
- 多核时,同时运行
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(2)
go HelloWorld()
go a()
fmt.Println("main")
time.Sleep(time.Second)
}
func HelloWorld() {
for i := 1; i < 10; i++ {
fmt.Println(i, "hello world")
}
}
func a() {
for i := 1; i < 10; i++ {
fmt.Println(i, "a")
}
}
03 channal
channal
是一种类型,是引用类型
channal实现goroutine
之间的通信
go是通过通讯共享内存
1 定义channel类型
语法
var 变量名 chan channel中的数据类型
package main
import "fmt"
func main() {
var c chan NT
fmt.Println(c)
}
type NT interface {
}
2 channel初始化
必须初始化才能使用,用make
函数初始化
package main
import "fmt"
func main() {
fmt.Println("ok")
ChannelFunc()
}
func ChannelFunc() {
var a chan int // 生命channel变量
a = make(chan int, 8) // 初始化channel变量,相当于a := make(chan int, 8)
fmt.Println(a)
}
3 channel操作
是管道,先进先出
共三种操作:
1 加值
2 取值
3 关闭管道
注意:
加值和取值都用<-
表示
语法
# 加值
"""
a := make(chan int, 8) // 8是通道的容量,也就是缓冲,当缓冲的余值为0时,再往通道里加数据会报错
a <- 10
"""
# 取值
"""
b := <- a
"""
# 关闭
"""
close(a)
"""
package main
import "fmt"
func main() {
ChannelFunc()
}
func ChannelFunc() {
a := make(chan int, 8)
a <- 10
a <- 18
b := <-a
close(a)
fmt.Println(b)
}
注意:
1 通道是会被垃圾回收机制回收的,所以手动关闭通道不是必须的
2 关闭通道后,继续向关闭的通道加数据,会报错
3 关闭通道后,可以向关闭的通道取数据,数据取完会取指定类型的零值
4 关闭的通道再关闭操作,会报错
例子:
package main
import "fmt"
func main() {
ChannelFunc()
}
func ChannelFunc() {
a := make(chan int, 8)
a <- 10
a <- 18
close(a)
b := <- a // 10
c := <- a // 18
d := <- a // 0
f := <- a // 0
fmt.Println(b, c, d, f)
}
4 通道通信
- 没缓冲的通道
不能直接向通道里加数据,需要用一个goroutine
去接收
package main
import (
"fmt"
)
func main() {
a := make(chan int)
go Recv(a) // 必须要在加入值之前开启一个goroutine去取数据
a <- 10
fmt.Println("main")
}
func Recv(a chan int) {
c := <- a
fmt.Println(c)
}
- 有缓冲的通道
可以直接向管道里加数据
package main
import (
"fmt"
)
func main() {
a := make(chan int, 10)
a <- 10
fmt.Println("main")
}
- for range 从通道中取值
package main
import "fmt"
func main() {
c := make(chan int)
go Send(c)
for v := range c {
fmt.Println(v)
}
}
func Send(c chan int) {
for i := 0; i < 1000; i++ {
c <- i
}
close(c)
}
注意:
开启接收数据的goroutine
一般放在向通道内加数据的前面
- 单向通道
定义通道时,单向通道可以分为
只 写:chan <- 类型
只读:<- chan 类型
package main
import "fmt"
func main() {
c := make(chan int)
go Send(c)
Recv(c)
fmt.Println("main")
}
func Send(c chan<- int) {
for i := 0; i < 1000; i++ {
c <- i
}
close(c)
}
func Recv(c <-chan int) {
for v := range c {
fmt.Println(v)
}
}
04 select的多路复用
同时从多个通道内取值
package main
import (
"fmt"
)
func main() {
c := make(chan int, 100)
for i := 0; i < 100; i++ {
select {
case a := <-c:
fmt.Println("a: ", a)
case c <- i:
fmt.Println("i: ", i)
default:
fmt.Println("fail")
}
}
}
注意:
1 处理多个通道的:取值或者加值的操作
2 同时满足多个case时,随意选择一个
05 锁
多个goroutine
同时操作同一个数据时,会造成脏数据,数据竞态问题
互斥锁
06 并发任务同步
等待所有的goroutine
的运行完毕,再继续往下运行(再结束):sync.WaitGroup
循环调用goroutine,且用管道接收返回结果,拿到结果后才继续往下执行
package main
import (
"fmt"
"sync"
)
func main() {
var ws sync.WaitGroup
// 用有缓存的管道,不发生阻塞
numChan := make(chan int, 10)
for i := 1; i < 20; i++ {
ws.Add(1) //引用计数加一
go goTest(i, 10, numChan, &ws) // ws对象必须是指针
}
go func() { // 这里必须是一个新的goroutine,异步处理关闭管道的操作。确保关闭管道之前,所有的数据加入完毕。
ws.Wait() // 会在这里发生阻塞,引用计数为0时才会继续往下执行
close(numChan) // 关闭管道。保证关闭后不会再有新的数据添加
}()
for v := range numChan { // 获取管道中的数据
fmt.Println(v)
}
}
func goTest(num1, num2 int, numChan chan int, ws *sync.WaitGroup) {
defer ws.Done() // 引用计数减1
numChan <- num1 + num2 // 加入管道
return
}
注意:
Add(数字)
:计数器加数字,一般情况下是1
Done()
:计数器减一
Wait()
:当计数器为0,才继续往下运行