2022-5-26 实习Day45

1、手工分报,点击账务报错问题排查,必输栏位前台校验要后续改进 –2小时 100%
2、汇出汇款选择直接解付查看账务可显示,待解付不显示账务资金流向,再点击解付依旧不显示问题排查 –3小时 60%


Golang编程学习(part 40)

1、channel(管道)机制的引入

【1】现在要计算1-200的各个数的阶乘,并且把各个数的阶乘放入到map中,最后显示出来,要求使用goroutine完成。

分析思路:

  • 使用goroutine来完成,效率高,但是会出现并发/并行安全问题
  • 这里就提出了不同goroutine如何通信的问题

代码实现:

  • 使用goroutine来完成(看看使用goroutine并发完成会出现什么问题?然后我们会去解决)
  • 在运行某个程序时,如何知道是否存在资源竞争问题。方法很简单,在编译该程序时增加一个参数 - race 即可

image-20220712225003804

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

import (
"fmt"
"time"
)

// 思路
// 1、编写一个函数,来计算各个数到阶乘,并放入到map中
// 2、我们启动到协程多个,统计的将结果放入到map中
// 3、map应该做出一个全局的(这个map涉及到并发写操作,会报错,map类型go处理的会很严格)
var myMap = make(map[int]int, 200)

//test函数就是计算n!,让将这个结果放入到myMap
func test(n int) {
res := 1
for i := 1; i <= n; i++ {
res *= i
}
// 这里我们将res放入到myMap
myMap[n] = res
}

func main() {
// 我们这里开启多个协程完成这个任务[200个]
for i := 1; i <= 77; i++ {
go test(i)
}
// 休眠10秒钟[又是一个问题]
time.Sleep(time.Second * 10)
// 这里我们输出结果,变量这个结果
for key, value := range myMap {
fmt.Printf("map[%d]=%d\n", key, value)
}
}

【2】所以上述代码涉及到一个问题,就是不同goroutine之间如何通讯
  • 全局变量的互斥锁
  • 使用管道channel来解决

【3】使用全局变量加锁同步改进程序

因为没有对全局变量m加锁,因此会出现资源争夺问题,代码会出现错误,提示concurrent map writes

解决方案:加入互斥锁

我们的数的阶乘很大,结果会越界,改成+=

代码改进

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

import (
"fmt"
"sync"
"time"
)

// 思路
// 1、编写一个函数,来计算各个数到阶乘,并放入到map中
// 2、我们启动到协程多个,统计的将结果放入到map中
// 3、map应该做出一个全局的
var (
myMap = make(map[int]int)

//声明一个全聚德互斥锁
//sync是包:synchronized 同步
//Mutex:是互斥
lock sync.Mutex
)

//test函数就是计算n!,让将这个结果放入到myMap
func test(n int) {
res := 1
for i := 1; i <= n; i++ {
res *= i
}
// 加锁
lock.Lock()
// 这里我们将res放入到myMap
myMap[n] = res
// 解锁
lock.Unlock()
}

func main() {
// 我们这里开启多个协程完成这个任务[200个]
for i := 1; i <= 30; i++ {
go test(i)
}
// 休眠10秒钟[又是一个问题]
time.Sleep(time.Second * 10)
// 这里我们输出结果,变量这个结果
lock.Lock()
for key, value := range myMap {
fmt.Printf("map[%d]=%d\n", key, value)
}
lock.Unlock()
}

【4】为什么需要channel?
  • 前面使用全局变量加锁同步来解决goroutine的通讯,但是不完美
  • 主线程在等待所有goroutine全部完成的时间很难确定,我们这里设置10秒,仅仅是估算
  • 如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine处于工作状态,这是也会随主线程的退出而销毁
  • 通过全局变量加锁同步来实现通讯,也并不利用多个协程对全局变量的读写操作
  • 上面的分析都在呼唤一个新的通讯方式—channel


2、channel(管道)机制的使用

【1】channel的基本介绍
  • channel本质就是一个数据结构—队列

  • 数据是先进先出(FIFO:first in first out)

  • 线程安全,多个goroutine访问时不需要加锁,就是说channel本身就是线程安全的

  • channel是有类型的,一个string的channel只能存放string类型数据

  • 示意图:

    image-20220713000520149


【2】channel的定义和声明
1
2
3
4
5
6
7
8
9
10
11
12
var 变量名 chan 数据类型

举例:
var intChan chan int // intChan用于存放int类型数据
var mapChan chan map[int]string // mapChan用于存放map[int]string类型
var perChan chan Person //
var perChan chan Person //

说明:
channel是`引用类型`
channel必须初始化才能写入数据,即make后才能使用
管道是有类型的,intChan只能写入整数int

【3】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
25
26
27
28
29
30
31
32
33
34
35
package main

import "fmt"

func main() {
// 演示一下管道的使用
// 1、创建一个可以存放3个int类型的管道
var intChan chan int
intChan = make(chan int, 3)

// 2、看看intChan是什么?
fmt.Printf("intChan的值=%v intChan本身的地址=%p\n", intChan, &intChan)

// 3、向管道写入数据
intChan <- 10
num := 211
intChan <- num
intChan <- 50
// intChan <- 98 //注意点,当我们给管道写入数据时,不能超过其容量

// 4、看看管道的长度和cap(容量)
fmt.Printf("channel len=%v cap=%v \n", len(intChan), cap(intChan))

// 5、从管道中读取数据
var num2 int
num2 = <-intChan
fmt.Println("num2=", num2)
fmt.Printf("channel len=%v cap=%v \n", len(intChan), cap(intChan)) // 2,3

// 6、在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告deadlock
num3 := <-intChan
num4 := <-intChan
num5 := <-intChan
fmt.Println("num3=", num3, "num4=", num4, "num5=", num5)
}

【4】channel使用的注意事项
  • channel中只能存放指定的数据类型
  • channel的数据放满后就不能再放入了
  • 如果从channel取出数据后,可以继续放入
  • 在没有使用协程的情况下,如果数据取完了,再去取,就会报dead lock