### sync包
go語言多線程,子線程運行時間大于主線程時,主線程運行完畢就會關閉程序,屆時子線程沒運行完。
示例代碼:創建一個主線程和一個test的子線程,主線程和子線程都使用for循環輸出i,子線程時間間隔為3秒,主線程為1秒。子線程執行時間大于主線程
```
package main
import (
"fmt"
"time"
)
// 子進程函數
func test() {
for i := 0; i < 10; i++ {
fmt.Println("子線程輸出", i)
// 這里加一個子線程堵塞時間間隔 3秒 比主線程少1秒
time.Sleep(time.Second * 3)
}
}
// main函數的是主線程
func main() {
// 如果不加關鍵詞 go 就會順序執行 先執行 test函數的
// 加關鍵詞 go 但是test函數沒有執行, 是因為主線程運行太快直接結束關閉了程序
go test()
for i := 0; i < 10; i++ {
fmt.Println("主線程輸出", i)
// 這里個主線程加一個時間堵塞,但是時間比子線程少
time.Sleep(time.Second)
}
// 所以 需要加 一個休眠時間堵塞一下
time.Sleep(time.Second)
fmt.Println("主線程結束")
}
//總結:
// go語言多線程 需要加時間堵塞 才能運行
// 子線程運行時間大于主線程時,主線程運行完畢就會關閉程序,屆時子線程沒運行完
```
基于上面的示例代碼,使用sync包里面**sync.WaitGroup** (等待組)
示例代碼:和上面一樣,這里使用sync包里面的等待組sync.WaitGroup
第一步創建全局變量,wg類型sync.WaitGroup
第二步使用Add()函數協程計數器加1
第三步,子進程結束使用Done()函數協程計數器減1
第四步主進程使用Wait()函數等待子進程
```
package main
import (
"fmt"
"sync"
"time"
)
// 第一步 創建一個全局變量 wg 類型是sync.WaitGroup
var wg sync.WaitGroup
// 子進程函數
func test() {
for i := 0; i < 10; i++ {
fmt.Println("子線程輸出", i)
time.Sleep(time.Second * 2)
}
// 第三步,子進程結束 使用sync包里面的 Done()函數 協程計數器加-1
wg.Done()
}
// main函數的是主線程
func main() {
// 第二步,調用協程時,使用sync包里面的 Add()函數 協程計數器加1
wg.Add(1)
go test()
for i := 0; i < 10; i++ {
fmt.Println("主線程輸出", i)
time.Sleep(time.Second)
}
// 第四步,調用協程時,使用sync包里面的 Wait()函數 主線程等待子線程結束
wg.Wait()
fmt.Println("主線程結束")
}
// 總結:這樣就會一直等到子線程直線完畢 程序才關閉
```
```
package main
import (
"fmt"
"sync"
)
func main() {
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i++ {
go func(i int) {
fmt.Println(i)
wg.Done()
}(i)
}
wg.Wait()
}
```
多個協程示例代碼
```
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
// 子進程函數
func test(num int) {
for i := 0; i < 10; i++ {
fmt.Println("線程", num, "輸出", i)
// time.Sleep(time.Second * 2) 堵塞2秒
time.Sleep(time.Millisecond * 100) // 100毫秒
}
wg.Done()
}
// main函數的是主線程
func main() {
for i := 0; i < 3; i++ {
wg.Add(1)
go test(i)
}
for i := 0; i < 10; i++ {
fmt.Println("主線程輸出", i)
time.Sleep(time.Millisecond * 20) // 20毫秒
}
wg.Wait()
fmt.Println("主線程結束")
}
```
互斥鎖 **sync.Mutex**
加互斥鎖主要解決(編譯后)資源競爭的問題。比如多個線程搶占一個資源,例如多個協程讀取數據庫里面的一條內容。加了鎖之后,只有一個協程讀取,其他的等待它讀取完再執行操作。
示例代碼:創建變量j初始值為0,test函數執行一次,j就會遞增。如果多個進程同時使j遞增的話,就可能因為進程搶占資源報錯。但是加了互斥鎖之后,就會等一個協程執行完之后,再執行另外一個。就不會發生資源搶占而報錯。
```
package main
import (
"fmt"
"sync"
)
// 邏輯中使用的某個變量
var j = 0
// 與變量對應的使用互斥鎖
var mutex sync.Mutex
// 等待組
var wg sync.WaitGroup
// 子進程
func test() {
// 邏輯開始加互斥鎖
mutex.Lock()
// 邏輯遍歷遞增
j++
fmt.Println("邏輯變量", j)
// 去邏輯鎖
mutex.Unlock()
// 協程計數器減1
wg.Done()
}
//主進程
func main() {
for i := 0; i < 10; i++ {
// 協程計數器加1
wg.Add(1)
go test()
}
// 等待子進程結束關閉程序
wg.Wait()
}
```
讀寫互斥鎖 **sync.RWMutex**
讀寫互斥鎖定,可以讓多個讀操作并發,同時讀取,但是對于寫操作是完全互斥的。也就是說當一個協程進行寫操作的時候,其他協程不能進行讀操作也不也能寫操作。
示例代碼:10個進程執行寫的操作,10個進程執行讀的操作,加上讀寫互斥鎖之后,寫的操作是互斥的,只能一個進程操作完,下個進程才能操作。讀的時候是并行的。
```
package main
import (
"fmt"
"sync"
"time"
)
// 等待組
var wg sync.WaitGroup
// 讀寫互斥鎖
var mutex sync.RWMutex
// 寫操作的協程
func write() {
// 加互斥鎖
mutex.Lock()
fmt.Println("@@@執行寫操作")
time.Sleep(time.Millisecond * 1000) //1000毫秒
// 去鎖
mutex.Unlock()
wg.Done()
}
func read() {
// 加讀寫互斥鎖
mutex.RLock()
fmt.Println("執行讀操作$$$")
time.Sleep(time.Millisecond * 1000) //1000毫秒
// 去鎖
mutex.RUnlock()
wg.Done()
}
func main() {
// 10個協程執行寫的操作
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
// 10協程執行讀的操作
for i := 0; i < 10; i++ {
wg.Add(1)
go read()
}
wg.Wait()
}
```
- 安裝開發環境
- 安裝開發環境
- 安裝詳細教程
- 引入包
- Go語言基礎
- 基本變量與數據類型
- 變量
- 數據類型
- 指針
- 字符串
- 代碼總結
- 常量與運算符
- 常量
- 運算符
- 流程控制
- if判斷
- for循環
- switch分支
- goto跳轉
- 斐波那契數列
- Go語言內置容器
- 數組
- 切片
- 映射
- 函數
- 函數(上)
- 函數(中)
- 函數(下)
- 小節
- 包管理
- 結構體
- 結構體(上)
- 結構體(中)
- 結構體(下)
- 小節
- 錯誤處理
- 錯誤處理
- 宕機
- 錯誤應用
- 小節
- 文件操作
- 獲取目錄
- 創建和刪除目錄
- 文件基本操作(上)
- 文件基本操作(中)
- 文件基本操作(下)
- 處理JSON文件
- 接口與類型
- 接口的創建與實現
- 接口賦值
- 接口嵌入
- 空接口
- 類型斷言(1)
- 類型斷言(2)
- 小節
- 并發與通道
- goroutine協程
- runtime包
- 通道channel
- 單向通道channel
- select
- 線程同步
- 多線程的深入學習
- http編程
- http簡介
- Client和Request
- get請求
- post請求
- 模塊函數方法
- 模塊
- fmt庫,模塊
- 項目練習
- 爬蟲:高三網
- 爬蟲:快代理
- 爬蟲:快代理2
- 多線程:通道思路
- 多線程爬蟲:快代理