Day11(并发编程)
概念
- 并发:把任务在不同的时间点交给处理器进行处理同一时间点,任务并不会同时进行
- 并行:把每一个任务分配给每一个处理器独立完成,在同一时间点任务一定是同时运行的
简介使用
- 创建:使用
go
关键字创建一个 goroutinego functionName()
-
调整并发运行性能
-
例子:
1 2 3 4 5 6 7 8 9
runtime.GOMAXPROCS(number) number -> 逻辑CPU数量 number < 1 不修改任务参数 = 1 单核心执行 > 1 多核并发执行 cpuNUmber := runtime.NumCPU() // 查询CPU数量 fmt.Println(cpuNUmber) runtime.GOMAXPROCS(cpuNUmber)
-
批注
- Go1.5 之前 默认使用单核执行,之后默认执行上面语句,以便让代码并发执行,最大效率的利用 CPU
- GOMAXPROCS 也是环境变量,在应用启动前设置环境变量也可以起到相同的作用
-
通道(channel)
在多个 goroutine 间通信的管道
并发编程
goroutine
- 时间片轮转以及调度
goroutine
协程 使用go
关键字创建,并发执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main
import (
"fmt"
"time"
)
func task() {
var i int
for {
i++
fmt.Println("new Task")
if i == 3 {
break
}
time.Sleep(time.Second)
}
}
func main() {
go task()
var i int
for {
i++
fmt.Println("main func")
if i == 3 {
break
}
time.Sleep(time.Second)
}
}
1
2
3
4
5
new Task
main func
main func
new Task
main func
-
主协程退出其他子协程也会退出
下面程序只会打印
main
1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func main(){
go func () {
for i := 0; i < 5; i++ {
fmt.Println("子协程")
}
}()
fmt.Println("main")
}
-
runtime
工具包Gosched
、Goexit
、GOMAXPROCS
Gosched
让出时间片 先让别的协程,执行再执行此协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
package main import ( "fmt" "runtime" ) func main() { go func() { for i := 0; i < 3; i++ { fmt.Println("Cc") } }() for i := 0; i < 2; i++ { runtime.Gosched() fmt.Println("Lcc") } }
1 2 3 4 5
Cc Cc Cc Lcc Lcc
Goexit
终止此协程与return
不同后者终止次函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
package main import ( "fmt" "runtime" "time" ) func exit() { fmt.Println("exit a") // return runtime.Goexit() fmt.Println("exit b") } func main() { go func() { fmt.Println("go func 1") exit() fmt.Println("go fun 2") }() // 等待 time.Sleep(time.Second * 5) }
1 2
go func 1 exit a
GOMAXPROCS
指定核数运行 1~4 时打印交叉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
package main import ( "fmt" "runtime" "time" ) func main() { runtime.GOMAXPROCS(4) for i := 0; i < 100; i++ { go fmt.Print(1) fmt.Print(0) } time.Sleep(time.Second * 2) }
channel
资源竞争共享内存
- 每使用 channel 造成资源竞争和共享内存问题,两个协程同时使用
printer
打印值会造成不一,不会同步,把相关的ch
注释且可以解决
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main
import (
"fmt"
"time"
)
// var ch = make(chan int)
func printer(word string) {
for _, v := range word {
fmt.Printf("%c", v)
time.Sleep(time.Second)
}
}
// lcc 打印 title
func lcc() {
// ch <- 1
printer("title")
}
// Cc 打印 word
func Cc() {
// <- ch
printer("word")
}
func main() {
go lcc()
go Cc()
time.Sleep(time.Second * 10)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import (
"fmt"
)
func main() {
//创建channel
ch := make(chan string)
defer fmt.Println("main end")
go func() {
defer fmt.Println("func end")
for i := 0; i < 2; i++ {
fmt.Println("Func for")
}
ch <- "ch wr"
}()
str := <-ch //没有数据前,阻塞
fmt.Println("rd = ", str)
}
-
无缓冲与有缓冲
- 无缓冲接收前,不会保存任何通道值,需要发送端和接收端同时准备好才能完成操纵,则会造成堵塞等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
package main import ( "fmt" "time" ) func main() { //创建一个无缓存的channel ch := make(chan int, 0) //len(ch)缓冲区剩余数据个数, cap(ch)缓冲区大小 fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch)) //新建协程 go func() { for i := 0; i < 3; i++ { fmt.Printf("子协程:i = %d\n", i) ch <- i //往chan写内容 } }() //延时 time.Sleep(2 * time.Second) for i := 0; i < 3; i++ { num := <-ch //读管道中内容,没有内容前,阻塞 fmt.Println("num = ", num) } }
- 有缓存接收前,会保存一个或多个通道值,只有通道没有接收值才会堵塞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
package main import ( "fmt" "time" ) func main() { //创建一个有缓存的channel ch := make(chan int, 3) //len(ch)缓冲区剩余数据个数, cap(ch)缓冲区大小 fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch)) //新建协程 go func() { for i := 0; i < 10; i++ { ch <- i //往chan写内容 fmt.Printf("子协程[%d]: len(ch) = %d, cap(ch)= %d\n", i, len(ch), cap(ch)) } }() //延时 time.Sleep(2 * time.Second) for i := 0; i < 10; i++ { num := <-ch //读管道中内容,没有内容前,阻塞 fmt.Println("num = ", num) } }
-
关闭
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import "fmt"
var ch = make(chan int)
func main() {
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
for {
if i, ok := <-ch; ok == true {
fmt.Println(i)
} else {
break
}
}
}
1
2
3
4
5
0
1
2
3
4
- 单方向
channel
(生产者、消费者)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
import "fmt"
// write 写
func write(write chan<- string) {
for i := 0; i < 3; i++ {
write <- fmt.Sprint("Cc", i)
}
defer close(write)
}
// read 读
func read(read <-chan string) {
for v := range read {
fmt.Println(v)
}
}
func main() {
ch := make(chan string)
go write(ch)
read(ch)
}
1
2
3
Cc 写0
Cc 写1
Cc 写2
sync
sync.WaitGroup
,等待子协程执行完之后再退出主协程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import (
"fmt"
"sync"
)
func main() {
var group sync.WaitGroup
// 添加两个
group.Add(2)
go func() {
for i := 0; i < 3; i++ {
fmt.Println("func1 --->", i)
}
// 执行完之后去除一个执行完毕
group.Done()
}()
go func() {
for i := 0; i < 4; i++ {
fmt.Println("func2 --->", i)
}
// 去除一个执行完毕
group.Done()
}()
// 等待group 数量为0 ,如果Done()方法数量和添加数量不一至,会造成deadlock! 死锁!
group.Wait()
}
1
2
3
4
5
6
7
func2 ---> 0
func2 ---> 1
func2 ---> 2
func2 ---> 3
func1 ---> 0
func1 ---> 1
func1 ---> 2
This post is licensed under
CC BY 4.0
by the author.