go并发相关学习01

go并发相关学习01

1.基础

  • c语言并发最小单位是线程,即多线程。
  • go语言中不是线程,而是go程->goroutine。
  • 一个go程占用的系统资源远远小于线程,默认大约需要4k-5k的内存资源。
  • 使用go程,只要在目标函数前加上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
package main

import (
"fmt"
"time"
)

func disPlay() {
count := 1
for {
fmt.Println("======子go程:", count)
count++
time.Sleep(500 * time.Millisecond)
}
}

func main() {

//子go程
go disPlay()

//主go程
count := 1
for {
fmt.Println("主go程:", count)
count++
time.Sleep(1 * time.Second)
}

}
1
2
3
4
5
6
7
8
9
go程: 1      
======子go程: 1
======子go程: 2
======子go程: 3
主go程: 2
======子go程: 4
主go程: 3
======子go程: 5
======子go程: 6

2.提前退出

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
package main

import (
"fmt"
"runtime"
"time"
)

//GOEXIT 提前退出当前go程
//return 退出当前函数
//exit 退出当前进程
func main() {
go func() {
func() {
fmt.Println("这是子go程内部的函数!")
//return //只是返回当前函数
//os.Exit(-1) //退出进程
runtime.Goexit() //退出子go程
}()

fmt.Println("子go程结束!")
}()

fmt.Println("这是主go程!")
time.Sleep(3 * time.Second)
fmt.Println("OVER!")
}

3.管道

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
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"fmt"
"time"
)

//当涉及到多go程时,c语言使用互斥量,通过锁来保证资源同步,避免竞争。
//go也支持这种方式,但是go更好的解决方案是使用管道、通道。
// A 往通道里写入数据,B 从通道中读取数据,go已经做好了数据同步。
func main() {

//创建的时候不指定容量,则表示无缓冲的通道
//var numChan chan int //默认是nil
//使用管道的时候一定要make,同map一样,否则是nil
//numChan := make(chan int) //数字管道,size字段不传,默认是0,还是一个nil管道
numChan := make(chan int, 5)

go func() {
time.Sleep(2 * time.Second)
for i := 0; i < 20; i++ {
data := <-numChan
fmt.Println("这是子go程2, data:", data)
}
}()

//time.Sleep(1 * time.Second)

go func() {
for i := 100; i < 110; i++ {
numChan <- i
fmt.Println("这是子go程2,写入数据:", i)
}
}()

for i := 0; i < 10; i++ {
numChan <- i
fmt.Println("这是主go程,写入数据:", i)
}

time.Sleep(5 * time.Second)
}

有缓存通道输出(日志输出存在竞争)

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
32
33
34
35
36
37
38
39
40
是主go程,写入数据: 0
这是主go程,写入数据: 1
这是主go程,写入数据: 2
这是主go程,写入数据: 3
这是子go程2,写入数据: 100
这是子go程2, data: 0
这是子go程2, data: 100
这是子go程2, data: 1
这是子go程2, data: 2
这是子go程2, data: 3
这是子go程2, data: 4
这是子go程2, data: 101
这是子go程2,写入数据: 101
这是子go程2,写入数据: 102
这是子go程2,写入数据: 103
这是子go程2,写入数据: 104
这是子go程2,写入数据: 105
这是子go程2,写入数据: 106
这是子go程2,写入数据: 107
这是子go程2, data: 102
这是子go程2, data: 103
这是子go程2, data: 104
这是子go程2, data: 105
这是子go程2, data: 106
这是子go程2, data: 107
这是子go程2, data: 108
这是子go程2,写入数据: 108
这是子go程2,写入数据: 109
这是主go程,写入数据: 4
这是主go程,写入数据: 5
这是主go程,写入数据: 6
这是主go程,写入数据: 7
这是主go程,写入数据: 8
这是主go程,写入数据: 9
这是子go程2, data: 109
这是子go程2, data: 5
这是子go程2, data: 6
这是子go程2, data: 7
这是子go程2, data: 8
这是子go程2, data: 9

无缓存通道输出(日志输出存在竞争)

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
32
33
34
35
36
37
38
39
40
这是子go1, data: 0
这是子go1, data: 100
这是主go程,写入数据: 0
这是子go2,写入数据: 100
这是主go程,写入数据: 1
这是子go1, data: 1
这是子go1, data: 101
这是子go1, data: 2
这是子go2,写入数据: 101
这是子go2,写入数据: 102
这是主go程,写入数据: 2
这是子go1, data: 102
这是子go1, data: 103
这是子go1, data: 3
这是子go2,写入数据: 103
这是子go2,写入数据: 104
这是主go程,写入数据: 3
这是子go1, data: 104
这是子go1, data: 105
这是子go1, data: 4
这是子go2,写入数据: 105
这是子go2,写入数据: 106
这是主go程,写入数据: 4
这是子go1, data: 106
这是子go1, data: 107
这是子go1, data: 5
这是子go2,写入数据: 107
这是子go2,写入数据: 108
这是主go程,写入数据: 5
这是子go1, data: 108
这是子go1, data: 109
这是子go1, data: 6
这是子go2,写入数据: 109
这是主go程,写入数据: 6
这是主go程,写入数据: 7
这是子go1, data: 7
这是子go1, data: 8
这是主go程,写入数据: 8
这是主go程,写入数据: 9
这是子go1, data: 9

写 > 读+缓存空间 程序阻塞在写入的位置。

读>写 程序阻塞在读取的位置。

主go程出现读或写阻塞会出现崩溃,错误原因deadlock

子go程出现读写阻塞会一直卡住,不会崩,可以利用这一点,但是如果逻辑错误导致永久死锁时,会有内存泄漏。

4.for-range遍历管道

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
package main

import (
"fmt"
"time"
)

func main() {

numChan := make(chan int, 10)

go func() {
for i := 100; i < 120; i++ {
numChan <- i
fmt.Println("这是子go程2,写入数据:", i)
}
close(numChan)
fmt.Println("写入完成,关闭管道!")
}()

//for range是不知道管道是否已经写完的,所以会一直在这里等待
//在写入端,将管道关闭,for range遍历关闭的管道时,会退出。
for v := range numChan {
fmt.Println("读取数据:", v)
}

time.Sleep(1 * time.Second)
fmt.Println("OVER!")

}
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
32
33
34
35
36
37
38
39
40
41
42
这是子go程2,写入数据: 100
这是子go程2,写入数据: 101
这是子go程2,写入数据: 102
这是子go程2,写入数据: 103
这是子go程2,写入数据: 104
这是子go程2,写入数据: 105
这是子go程2,写入数据: 106
这是子go程2,写入数据: 107
这是子go程2,写入数据: 108
这是子go程2,写入数据: 109
这是子go程2,写入数据: 110
读取数据: 100
读取数据: 101
读取数据: 102
读取数据: 103
读取数据: 104
读取数据: 105
读取数据: 106
读取数据: 107
读取数据: 108
读取数据: 109
读取数据: 110
读取数据: 111
这是子go程2,写入数据: 111
这是子go程2,写入数据: 112
这是子go程2,写入数据: 113
这是子go程2,写入数据: 114
这是子go程2,写入数据: 115
这是子go程2,写入数据: 116
这是子go程2,写入数据: 117
这是子go程2,写入数据: 118
这是子go程2,写入数据: 119
写入完成,关闭管道!
读取数据: 112
读取数据: 113
读取数据: 114
读取数据: 115
读取数据: 116
读取数据: 117
读取数据: 118
读取数据: 119
OVER!

5.管道总结

  1. 当管道写满了,写阻塞。
  2. 当缓冲区读完了,读阻塞。
  3. 如果管道没有使用make分配空间,管道默认是nil,从nil管道读取数据,写入数据,都会阻塞,但是不会崩溃
  4. 从一个已经close的管道可以正常读取未消费的数据,当没有数据可读时,会返回零值
  5. 向一个已经close的管道写入数据时,会崩溃
  6. 关闭一个已经close的管道,会崩溃
  7. 关闭管道的动作一定要做在写端执行,不应该放在读端,否则写段继续写会崩溃
  8. 读写的次数,一般需要对等,否则:
    1. 在多个go程中:资源泄漏
    2. 在主go程中,程序崩溃(deadlock)

6.判断管道状态

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
32
33
34
35
36
package main

import (
"fmt"
"time"
)

func main() {
numChan := make(chan int, 10)

//write
go func() {
for i := 0; i < 5; i++ {
numChan <- i
fmt.Println("write data:", i)
}
close(numChan)
fmt.Println("write close")
}()

//read
go func() {
for {
v, ok := <-numChan //ok-idom模式判断
if !ok {
fmt.Println("chan closed, ready exit!")
break
}
fmt.Println("v:", v)
}
}()

time.Sleep(5 * time.Second)
fmt.Println("over")

}
1
2
3
4
5
6
7
8
9
10
11
12
write data: 0
write data: 1
write data: 2
write data: 3
write data: 4
write close
v: 0
v: 1
v: 2
v: 3
v: 4
chan closed, ready exit!

7.单向通道

1
numChan := make(chan int, 10)	//双向通道,即可读也可写

单向通道:为了明确语义,一般用于函数参数

  • 单向读:var numChanReadOnly <- chan int
  • 单向写:var numChanWriteOnly chan <- int
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import (
"fmt"
"time"
)

func main() {
//单向读:
//var numChanReadOnly <-chan int
//单向写:
//var numChanWriteOnly chan<- int

numChan := make(chan int, 50)
//双向管道可以赋值给同类型的单向管道,单向不能转双向

go producer(numChan)

go consumer(numChan)

time.Sleep(2 * time.Second)
close(numChan)
time.Sleep(1 * time.Second)

fmt.Println("over")
}

//producer ==>提供一个只写的通道
func producer(out chan<- int) {
for i := 0; i < 10; i++ {
out <- i
fmt.Println("write data:", i)
}
fmt.Println("producer exit")
}

func consumer(in <-chan int) {
for {
v, ok := <-in
if !ok {
fmt.Println("chan close, ready exit")
break
}
fmt.Println("v:", v)
}
fmt.Println("consumer exit")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
write data: 0
write data: 1
write data: 2
write data: 3
write data: 4
write data: 5
write data: 6
write data: 7
write data: 8
write data: 9
producer exit
v: 0
v: 1
v: 2
v: 3
v: 4
v: 5
v: 6
v: 7
v: 8
v: 9
chan close, ready exit
consumer exit
over

8.select

当程序中有多个channel协同工作时,select监听多个通道,管道被触发时(写入,读取,关闭),可以进行对应处理,select的使用和switch类似,但是select的对象是io。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package main

import (
"fmt"
"strconv"
"time"
)

func main() {
numChan := make(chan int, 10)

strChan := make(chan string, 10)

go func() {
for {
fmt.Println("select...")
select {
case data1 := <-numChan:
fmt.Println("Read from numChan:", data1)
case data2 := <-strChan:
fmt.Println("Read from strChan:", data2)

// default: //这里会像switch一样,如果前面的通道没有信号,那么这里会一直被触发
// fmt.Println("default")
// time.Sleep(1 * time.Second)
}

}
}()

go func() {
for i := 0; i < 10; i++ {
numChan <- i
time.Sleep(300 * time.Millisecond)
}
//close(numChan)
fmt.Println("numChan exit")
}()

go func() {
str := "string"
for i := 0; i < 10; i++ {
tmp := str + strconv.Itoa(i)
strChan <- tmp
time.Sleep(300 * time.Millisecond)
}
//close(strChan)
fmt.Println("strChan exit")
}()

time.Sleep(100 * time.Second)
fmt.Println("over")
}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
elect...
Read from numChan: 0
select...
Read from strChan: string0
select...
Read from strChan: string1
select...
Read from numChan: 1
select...
Read from strChan: string2
select...
Read from numChan: 2
select...
Read from strChan: string3
select...
Read from numChan: 3
select...
Read from strChan: string4
select...
Read from numChan: 4
select...
Read from strChan: string5
select...
Read from numChan: 5
select...
Read from strChan: string6
select...
Read from numChan: 6
select...
Read from strChan: string7
select...
Read from numChan: 7
select...
Read from strChan: string8
select...
Read from numChan: 8
select...
Read from strChan: string9
select...
Read from numChan: 9
select...
strChan exit
numChan exit
over

本文标题:go并发相关学习01

文章作者:Tokey

发布时间:2021年06月30日 - 22:06

最后更新:2021年07月04日 - 10:07

原始链接:http://TokeyRoad.github.io/2021/06/30/go并发相关学习01/

许可协议: 转载请保留原文链接及作者。

0%