## 一、并發使用同一數據庫局柄
并發使用同一數據庫連接會產生爭搶的問題,導致報錯。
解決方案:
方法一:使用連接池(推薦)
方法二:使用同一個數據庫連接(不推薦)
## 二、多層循環嵌套查數據
所謂比對數據就是比對兩個 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
}
```
## 十九、關鍵字

## 二十、進程和線程說明
* 進程介紹程序在操作系統中的一次執行過程,是系統進行資源分配和調度的基本單位
* 線程只是進程的一個執行實例或流程,是程序執行的最小單元
* 一個進程可以有多個線程,但是一個線程只能對應一個進程
* 同一個進程中的多個線程可以并發執行
* 程序:運行起來的應用程序就稱為進程,也就是當程序不運行的時候我們稱為程序,當程序運行起來他就是一個進程,通俗的理解就是不運行的時候是程序,運行起來就是進程。程序只有一個,但是進程有多個
## 二十一、 同步和異步
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
}
```