## 一、并发使用同一数据库局柄
并发使用同一数据库连接会产生争抢的问题,导致报错。
解决方案:
方法一:使用连接池(推荐)
方法二:使用同一个数据库连接(不推荐)
## 二、多层循环嵌套查数据
所谓比对数据就是比对两个 db 的数据,即先从一个 db 中查出数据,然后根据主键再去查另一个 db 的数据,最后比对每一个字段数据是否一致。
这其中涉及循环嵌套查数据库,发现这样做效率比较低。
解决方案:
改为批量查询,然后并发比对数据。
## 三、过多的开启 Goroutine
1、如果不控制 Goroutine 的数量会出什么问题?
Goroutine 具备以下两个特点:
体积轻量(占内存小,一个 2kb 左右)
优秀的 GMP 调度([详见:图解 Golang 的 GMP 原理与调度流程](https://juejin.cn/post/6995091405563494431))
2、我们如果迅速的开启 goroutine (不控制并发的 goroutine 数量 )的话,会在短时间内占据操作系统的资源(CPU、内存、文件描述符等)。
## 四、控制 goroutine 的几种方法
方法一:channel 与 sync 同步组合方式实现控制 goroutine
```
package main
import (
"fmt"
"math"
"sync"
"runtime"
)
var wg = sync.WaitGroup{}
func doBusiness(ch chan bool, i int) {
fmt.Println("go func ", i, " goroutine count = ", runtime.NumGoroutine())
<-ch
wg.Done()
}
func main() {
//模拟用户需求go业务的数量
task_cnt := math.MaxInt64
ch := make(chan bool, 3)
for i := 0; i < task_cnt; i++ {
wg.Add(1)
ch <- true
go doBusiness(ch, i)
}
wg.Wait()
}
```
方法二:利用无缓冲 channel 与任务发送/执行分离方式
```
package main
import (
"fmt"
"math"
"sync"
"runtime"
)
var wg = sync.WaitGroup{}
func doBusiness(ch chan int) {
for t := range ch {
fmt.Println("go task = ", t, ", goroutine count = ", runtime.NumGoroutine())
wg.Done()
}
}
func sendTask(task int, ch chan int) {
wg.Add(1)
ch <- task
}
func main() {
ch := make(chan int) //无buffer channel
goCnt := 3 //启动goroutine的数量
for i := 0; i < goCnt; i++ {
//启动go
go doBusiness(ch)
}
taskCnt := math.MaxInt64 //模拟用户需求业务的数量
for t := 0; t < taskCnt; t++ {
//发送任务
sendTask(t, ch)
}
wg.Wait()
}
```
## 五、软件的架构模式总的说经历了三个阶段的演进:从单机、集中式到分布式微服务架构
第一阶段:单机架构,这个阶段通常采用面向过程的设计方法。通常采用C/S架构。
第二阶段:集中式架构,这个阶段通常采用面向对象的设计方法。一般采用经典的三层架构MVC,系统包括业务接入层、业务逻辑层和数据库层。
第三阶段:分布式微服务架构,微服务架构可以实现业务和应用之间的解耦。解决单体应用扩展性差、弹性伸缩能力不足的问题,非常适合在云计算环境下的部署和运营。
## 六、什么是DDD?
DDD (Domain Driven Design):领域驱动设计。
1\. 核心思想
DDD的核心思想就是避免业务逻辑的复杂性和技术实现的复杂性耦合在一起。
明确业务复杂性和技术复杂性的边界,隔离双方的复杂性,站在更高的角度实现解耦。
2\. 最大价值
DDD最大的价值就是梳理业务需求,抽象出一个个“领域”,并形成各个领域之间的接口交互,方便团队协作,推进项目前进。
## 七、微服务
**1\. 单一职责**
DDD思想指导我们对业务逻辑进行拆分,明确各自边界,形成不同的领域,不同的领域对应不同的微服务,这就是单一职责。
**2\. 团队独立**
不同的领域对应不同的业务团队,也对应着不同的技术团队,彼此之间是解耦的。
**3\. 技术独立**
不同的领域,不同的团队可以使用不同的开发语言,各自独立,只要按规范提供服务即可。
**4\. 数据库分离**
每个领域(每个服务)都拥有自己的数据源。
**5\. 独立部署**
每个领域(每个服务)都是独立的组件,可复用,可替换,降低耦合,易维护,易集群Docker部署服务
## 八、字符串和数字的相互转换(strconv)
字符串转整型
```
i, err := strconv.Atoi(aStr)
```
整型转字符串
```
anotherStr := strconv.Itoa(aInt)
```
## 九、获取时间
时间转字符串
```
now := time.Now()
strA := now.Format("2006-01-02 15:04:05")
```
## 十、 时间的比较
~~~
// 时间的加减
tenMinute, _ := time.ParseDuration("-10m") // 有效的时间单位是 "ns", "us" (或者 "µs"), "ms", "s", "m", "h"。
timeC := timeA.Add(tenMinute) // 拿到距离timeA 10分钟之前的时间
duration := timeC.Sub(timeA)
// 时间的比较
fmt.Println(duration.Minutes()) // 打印的值为-10
fmt.Println(timeA.Equal(timeB)) // 判断 timeA 是否等于 timeB,值为false,timeA和timeB因为时区不同,所以这两个时间不相等
fmt.Println(timeC.Before(timeA)) // 判断 timeA 是否小于 timeB,值为true
fmt.Println(timeA.After(timeB)) // 判断 timeA 是否大于 timeB,值为true
fmt.Println(timeB.After(timeA)) // 判断 timeA 是否大于 timeB,值为false
~~~
## 十一、切片
slice1 := []int{1, 2, 3}
fmt.Println(slice1) // [1 2 3]
slice2 := make([]int, 3) // 这里的3是数组的长度,是切片的初始长度
fmt.Println(slice2) // [0 0 0]
// 向切片中添加元素
slice2 = append(slice2, 1)
fmt.Println(slice2) // [0 0 0 1]
slice3 := make([]int, 2)
slice3 = append(slice3, []int{2, 3, 4}...)
fmt.Println(slice3) // [0 0 2 3 4]
// 获取切片的部分内容
fmt.Println(slice1[:]) // [1 2 3],slice[low:high],省略low之后low的默认值是0,省略high之后,high的默认值是切片的长度
fmt.Println(slice1[2:]) // [3]
fmt.Println(slice1[:1]) // [1]
// 将slice1中的元素复制到slice2中
copy(slice2, slice1)
fmt.Println(slice2) // [1 2 3 1]
// 遍历切片
for index, value := range slice2 {
fmt.Printf("索引%d,值%d\n", index, value)
}
var slice4 []string
fmt.Println(slice4 == nil) // true,声明的切片的默认值是nil
fmt.Println(len(slice4)) // 0,空的切片的默认长度是0
## 十二、映射
map1 := map[string]string{
"a_key": "a_value",
"b_key": "b_value"}
fmt.Println(map1) // map[a_key:a_value b_key:b_value]
map2 := make(map[int]string)
fmt.Println(map2) // map[]
map3 := map[string]interface{}{
"a": []int{1, 2},
"b": 1.23,
}
fmt.Println(map3) // map[a:[1 2] b:1.23]
// 从映射中获取对应键的值
fmt.Println(map3["a"]) // [1 2]
// 修改映射中对应键的值
map3["a"] = 1
fmt.Println(map3) // map[a:1 b:1.23]
// 遍历映射
for key, value := range map3 {
fmt.Printf("键:%v, 值:%v\n", key, value)
}
var map4 map[string]int
fmt.Println(map4 == nil) // true,声明的map的默认值是nil
fmt.Println(len(map4)) // 0,空map的长度为0
## 十三、接口
```
package main
import (
"fmt"
)
func main() {
aPerson := Person{
Name: "沫沫",
}
fmt.Println(aPerson.Dream("梦想成为闲人")) // 沫沫梦想成为闲人
}
type Behavior interface {
Dream(content string) string
}
type Person struct {
Name string
}
// 类型Person实现了接口Behavior
func (t Person) Dream(content string) string {
return fmt.Sprintf("%s%s", t.Name, content)
}
```
## 十四、延迟函数
```
package main
import (
"fmt"
)
func main() {
defer func() {
fmt.Println("a")
}()
defer func() {
fmt.Println("b")
}()
defer func() {
fmt.Println("c")
}()
fmt.Println("要执行的逻辑1")
fmt.Println("要执行的逻辑2")
}
```
## 十五、结构体转JSON
```
type Novel struct {
ID uint
Title string
Chapters []string
}
novel := Novel{
ID: 1,
Title: "我与掘金的二三事",
Chapters: []string{
"注册了账号",
"写了一篇文",
"又写了一篇文",
"升级了,开森",
},
}
a, err := json.Marshal(novel)
if err != nil {
fmt.Println(err)
}
os.Stdout.Write(a)
```
## 十六、JSON转结构体
```
type Novel struct {
ID uint
Title string
Chapters []string
}
novel := Novel{
ID: 1,
Title: "我与掘金的二三事",
Chapters: []string{
"注册了账号",
},
}
a, err := json.Marshal(novel)
if err != nil {
fmt.Println(err)
}
os.Stdout.Write(a)
fmt.Println("")
err = json.Unmarshal(a, &novel)
if err != nil {
fmt.Println(err)
}
fmt.Println(novel)
novel1JSON := []byte(`{"ID":2,"Title":"小步慢跑","Chapters":["ᕕ( ᐛ )ᕗ"]}`)
var novel1 Novel
err = json.Unmarshal(novel1JSON, &novel1)
if err != nil {
fmt.Println(err)
}
fmt.Println(novel1)
```
## 十七、创建通道
```
ch1 := make(chan int,10) // 创建一个能存储10个int类型数据的通道
ch2 := make(chan []int, 3) //创建一个能存储3个[]int 切片类型数据的通道
ch3 := make(chan interface{}) // 创建一个空接口类型的通道, 可以存放任意格式
type Equip struct{ /* 一些字段 */ }
ch4 := make(chan *Equip) // 创建Equip指针类型的通道, 可以存放*Equip
```
## 十八、通道的容量与长度
```
func main() {
// 创建一个通道
ch := make(chan int, 3)
fmt.Println("刚创建成功后:")
fmt.Printf("cap = %v,len = %v \n", cap(ch),len(ch)) //cap = 3,len = 0
ch <- 1
ch <- 2
fmt.Println("向通道中传入两个参数后:")
fmt.Printf("cap = %v,len = %v \n", cap(ch),len(ch)) //cap = 3,len = 2
<- ch
fmt.Println("从通道中取出一个值后:")
fmt.Printf("cap = %v,len = %v \n", cap(ch),len(ch)) //cap = 3,len = 1
}
```
## 十九、关键字
![](https://img.kancloud.cn/dd/93/dd93c38af760d16f620ff3f41bdacb1e_659x729.png)
## 二十、进程和线程说明
* 进程介绍程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位
* 线程只是进程的一个执行实例或流程,是程序执行的最小单元
* 一个进程可以有多个线程,但是一个线程只能对应一个进程
* 同一个进程中的多个线程可以并发执行
* 程序:运行起来的应用程序就称为进程,也就是当程序不运行的时候我们称为程序,当程序运行起来他就是一个进程,通俗的理解就是不运行的时候是程序,运行起来就是进程。程序只有一个,但是进程有多个
## 二十一、 同步和异步
1. 同步:描述的就是串行执行过程,多个任务按照顺序依次执行的过程
2. 异步:描述的就是并发和并行的过程,就是多个任务在一个时间段内同时执行,每个任务都不会等待其他任务执行完成后执行
## 二十二、 Go协程和Go主线程
Go主线程:一个Go线程上,可以起多个协程,协程是轻量级的线程
## 二十三、 go协程特点
* 有独立的栈空间
* 共享程序堆空间
* 调度由用户控制
* 协程是轻量级的线程
## 二十四、goroutinue基本使用
```
package main
import (
"fmt"
"runtime"
"strconv"
"time"
)
func test2() {
for i := 0; i <= 10; i++ {
fmt.Println("test中的值为:", strconv.Itoa(i))
time.Sleep(time.Second)
}
}
func main() {
//编写一个函数,每隔1s输出"test中的值为"
//要求主线程和gorutine同时执行
go test2()
//在主线程中,开启一个goroutine,该协程每隔1s输出"main中的值为"
for i := 1; i <= 10; i++ {
fmt.Println("main中的值为:", strconv.Itoa(i))
time.Sleep(time.Second)
}
//查询Golang运行的cpu数
fmt.Println(runtime.NumCPU()) //4
}
```