> # channel
- 長度和容量
~~~
package main
import "fmt"
func main() {
ch := make(chan struct{}, 3)
ch <- struct{}{}
fmt.Println(len(ch)) //輸出1
fmt.Println(cap(ch)) //輸出3
}
~~~
- 限制發送/接收類型
~~~
chan T // 可以接收和發送類型為 T 的數據
chan<- T // 只可以用來發送 T 類型的數據
<-chan T // 只可以用來接收 T 類型的數據
~~~
> # 底層數據結構
```
type hchan struct {
qcount uint // 通道中的當前元素個數
dataqsiz uint // 緩沖區的大小 (無緩沖時為0)
buf unsafe.Pointer // *環形緩沖區* 指向緩沖區的指針 ,緩沖區通過這個指針來存儲數據,已經發送但還未被接收的數據
elemsize uint16 // 單個元素的大小(如果通道是 `chan int` 類型,那么每個元素就是一個 `int`,`elemsize` 表示 `int` 類型的字節大小。)
closed uint32 // 標識 channel 是否已關閉 (0 表示未關閉,1 表示已關閉)
timer *timer //用于處理與該通道相關的超時操作
elemtype *_type //指向類型描述符的指針,它包含了通道中傳輸數據類型的所有信息
sendx uint // 下一個要發送元素的位置
recvx uint // 下一個要接收元素的位置 (每次讀取數據后,`recvx` 都會遞增,當到達緩沖區末尾時,`recvx` 會重置為 0,形成環形讀取操作)
recvq waitq // 等待接收數據的 goroutine 隊列
sendq waitq // 等待發送數據的 goroutine 隊列
lock mutex // 互斥鎖,用于保護 channel 的操作
}
```
> # 發送和接收數據的本質
- 向 channel 發送值類型會拷貝, 發送引用類型拷貝的是引用
~~~
package main
import "fmt"
func main() {
ch := make(chan []int, 1)
s := make([]int, 1)
s[0] = 1
ch <- s
s[0] = 2
fmt.Println(<-ch) //輸出2
}
~~~
> # 操作 nil channel, close channel, 正常 channel
| 操作 | nil channel | close channel | 正常 channel |
| --- | --- |--- |--- |
| close | panic: close of nil channel | panic: close of closed channel | 正常關閉|
| 讀操作 | fatal error: all goroutines are asleep - deadlock! | 有未接收的值可以正常讀, 沒有的話讀到對應類型的零值| 阻塞/正常讀數據|
| 寫操作 | fatal error: all goroutines are asleep - deadlock! | panic: send on closed channel | 阻塞/正常寫數據|
> # 使用 select 來多路復用 channel
- **隨機選擇**:當多個 `channel` 同時滿足條件時,`select` 會隨機選擇一個執行。
- **默認分支**:可以在 `select` 中添加 `default` 分支,當所有的 `channel` 都沒有數據時,`select` 可以立即執行 `default` 分支而不阻塞。
~~~
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan struct{})
ch2 := make(chan struct{})
go func() {
ch1 <- struct{}{}
}()
go func() {
ch2 <- struct{}{}
}()
select {
case <-ch1:
fmt.Println("ch1")
case <-ch2:
fmt.Println("ch2")
case <-time.After(2 * time.Second):
fmt.Println("超時")
default:
fmt.Println("default")
time.Sleep(1 * time.Second)
}
}
~~~
> # range channel
```
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
close(ch)
}()
//如果通道不被關閉,range 將會一直等待新的數據,不會自動退出循環
for v := range ch {
fmt.Println(v)
}
fmt.Println("Done")
}
```
> # 讀取關閉的channel
~~~
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
v, ok := <-ch
fmt.Println(v, ok) //1 true
v, ok = <-ch
fmt.Println(v, ok) //2 true
v, ok = <-ch
fmt.Println(v, ok) //0 false //沒數據了,返回對應類型的零值和false
}
~~~
> # 如何優雅的關閉 channel
- v, ok := <-ch 取值的時候加判斷, 關閉的channel, v 返回對應類型的零值, ok 返回 false。用這種方式去判斷channel 是否關閉有副作用, 會讀出channel里的元素
- [如何優雅的關閉Go Channel](https://www.ulovecode.com/2020/07/14/Go/Golang%E8%AF%91%E6%96%87/%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E5%85%B3%E9%97%ADGo-Channel/)
- 多生產者多消費者例子(關閉原則:不要在消費端關閉channel,不要在生產端有多個的并行時候執行關閉操作)
~~~
package main
import (
"fmt"
"sync"
"time"
)
func main() {
ch := make(chan int, 1024)
chClose := make(chan struct{})
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func(num int) {
for {
select {
case <-chClose:
fmt.Println("發送關閉1", num)
return
default:
}
select {
case <-chClose:
fmt.Println("發送關閉1", num)
return
case ch <- num:
}
}
}(i)
}
for i := 0; i < 10; i++ {
go func(num int) {
for {
select {
case <-chClose:
fmt.Println("接收關閉1", num)
wg.Done()
return
default:
}
select {
case <-chClose:
fmt.Println("接收關閉2", num)
wg.Done()
return
case v, _ := <-ch:
_ = v
}
}
}(i)
}
time.Sleep(time.Second * 3)
chClose <- struct{}{}
close(chClose)
wg.Wait()
}
~~~
> # 交替打印
- 可以通過runtime.GOMAXPROCS設置處理器數量為1, runtime.Gosched() 讓出當前調度
~~~
package main
import (
"fmt"
"time"
)
func main() {
//不能是 ch := make(chan struct{}, 1), 協程調度是隨機的, 如果緩存為1,會出現某個協程被執行多次
ch := make(chan struct{}, 0)
go func() {
for {
<-ch
fmt.Println(1)
ch <- struct{}{}
}
}()
go func() {
for {
<-ch
fmt.Println(2)
ch <- struct{}{}
}
}()
ch <- struct{}{}
time.Sleep(time.Hour)
}
~~~
- 草稿
- Golang
- 切片 slice
- 數組和切片的區別
- 左閉右開
- make([]int, 5) 和 make([]int, 0, 5) 區別
- 切片非線程安全,并發操作為啥不會像map一樣報錯
- []struct{} 如何遍歷
- 切片如何刪除某個元素
- append 一個nil 切片
- 哈希表 map
- 并發操作
- 并發寫報錯
- 并發讀不會報錯
- 并發讀有寫報錯
- 并發迭代有寫報錯
- 自制并發安全字典
- 官方并發安全字典
- 對未初始化的 map 進行賦值操作
- map的底層
- 無序輸出
- 等量擴容
- 實現集合
- map的key可以使哪些值
- 協程 go
- 協程相關閱讀
- 進程、線程、協程
- 協程 (捕獲異常 和 協程池)
- GPM 模型
- CSP模型
- channel
- channel 相關操作
- 交替打印
- 如何讓channel 只能接收/只能發送
- channel 常見報錯
- channel 死鎖
- nil channel 和 已關閉的 channel
- 使用 select 來多路復用 channel
- channel 的使用
- 接口和結構體
- 簡單使用
- 兩個結構體能否比較
- 工廠模式
- 概念
- 簡單工廠
- 方法工廠
- 堆和棧,值類型和引用類型,內存逃逸,垃圾回收
- 棧和堆
- 內存逃逸
- 值類型和引用類型
- 垃圾回收方式
- 性能優化分析工具 pprof
- golang 代碼片段
- 片段一 defer
- 片段二 channel
- Golang 相關
- Golang 相關閱讀
- Golang 1-10
- make 和 new 的區別
- 使用指針的場景
- Go語言的context包
- 位運算
- Copy 是淺拷貝還是深拷貝
- init 函數 和 sync.Once
- select 多路復用
- Golang 其它
- MongoDB
- 可比較類型 與 可轉json 類型
- Gorm
- 面向對象和面向過程
- go語言實現-面向對象
- go語言實現-面向過程
- 限流,熔斷,降級
- 了解
- 熔斷配置
- 熔斷例子
- 服務降級
- github.com/alibaba/sentinel-golang
- 互斥鎖 讀寫鎖 原子鎖
- 為什么需要鎖
- 互斥鎖
- 讀寫鎖
- 原子鎖
- 互斥鎖性能對比
- 原子鎖性能對比
- 互斥鎖 or 原子鎖?
- 條件鎖
- 計數器
- GoFrame
- GF1.16版本
- 修改使用的表
- 按天、周、月、年
- GoFrame 文檔
- 配置文件
- 生成腳本
- 排序算法
- 相關排序
- 冒泡排序
- 選擇排序
- 插入排序
- 快速排序
- 歸并排序
- 堆排序
- 數據庫
- 分布式怎么保證線程安全
- 數據庫實現方式
- 基于表記錄
- 樂觀鎖
- 悲觀鎖
- Redis實現方式
- Zookeeper實現方式
- Mysql 相關
- group_concat
- 索引優化
- 索引優化1
- 定期分析和優化索引
- 覆蓋索引
- 組合索引
- 聚簇索引和非聚簇索引
- 索引類型與方式、聚簇與非聚簇索引
- 事務特征和隔離級別
- 查詢優化
- mysql自增表插入數據時,Id不連續問題
- InnoDB引擎 和 MyISAM引擎區別
- 鎖
- 悲觀鎖和樂觀鎖
- 查詢,更新,插入語句
- 什么是死鎖
- 怎么處理死鎖
- MySQL 隔離級別
- 事務特征
- 隔離級別
- 廢棄3
- 索引
- 索引類型和方式、聚簇和非聚簇索引(上)
- 索引類型和方式、聚簇和非聚簇索引(下)
- 回表、覆蓋索引、最左前綴、聯合索引、索引下推、索引合并
- Mysql 優化
- 索引的原理
- 千萬級表修改表結構
- Redis
- 獲取隨機三條數據
- Redis 持久化方式
- 全量模式 RDB 冷備份(內存快照)
- 增量模式 AOF 熱備份(文件追加)
- 過期key的刪除策略、內存淘汰機制
- 數據結構
- 位圖
- 網絡
- 網絡相關
- 游戲同步方式:幀同步和狀態同步
- Websocket
- OSI模型
- TCP 與 UDP
- 三次握手四次揮手
- Http 狀態碼
- 1xx(信息性狀態碼)
- 101 服務端代碼
- 101 客戶端代碼
- 2xx(成功狀態碼)
- 3xx(重定向狀態碼)
- 302 服務端代碼
- 302 客戶端代碼
- 4xx(客戶端錯誤狀態碼)
- 5xx(服務器錯誤狀態碼)
- 如何排查接口問題
- 網絡請求和響應過程
- time_wait
- keep-alive
- http 和 rpc 的區別
- I/O多路復用 select和poll
- too many open file
- 其它技術
- git 相關操作
- 修改提交備注
- 多個提交合并成一個提交
- 回退版本
- 小程序和公眾號
- 消息模板
- 獲取code
- 靜默登錄
- 其它技術相關
- C盤空間不足
- 生成式人工智能AIGC
- 共享文件
- 接口文檔, mock提供測試數據
- 抓包工具
- Python
- 安裝包失敗
- 自動化測試 Scrapy
- AIGC:人工智能生成內容
- PHP
- xhprof 性能分析
- 一鍵安裝
- 哈希沖突的解決方式
- 鏈地址法(拉鏈法)
- 開放地址法
- 再哈希
- 概念1
- Nginx
- 負載均衡方式
- 加密解密
- 簡單了解
- 簽名算法例子
- 碼例子1
- 代碼例子2
- Linux
- netstat (用于查看和管理網絡連接和路由表)
- ps 用于查看和管理進程
- ab 壓測
- nohup 守護進程
- lsof (List Open File 獲取被進程打開文件的信息)
- tail 查看日志
- 各類linux同步機制
- Socket 服務端的實現,select 和epoll的區別?
- scp 傳輸,awk 是一個強大的文本分析工具
- pidof
- 項目
- 棋牌
- 牌的編碼
- 出牌規則
- 洗牌
- 股票
- 股票知識
- 龍虎榜數據緩存方式
- 單日龍虎榜數據
- 單只股票的歷史上榜
- 遇到的問題
- 浮點數精度問題
- Mysql Sum 精度問題(float, double精度問題)
- 分頁問題(數據重復)
- 工具包
- v3
- common.go
- common_test.go
- customized.go
- customized_test.go
- slice.go
- slice_test.go
- time.go
- time_test.go
- v4
- common.go
- common_test.go
- customized.go
- customized_test.go
- slice.go
- time.go
- time_test.go
- 相關閱讀
- 協程 goroutine
- 通道 channel
- json 和 gob 序列化和反序列化
- redis 有序集合
- mysql22
- 相關閱讀 s
- pyTorch
- defer
- 內存泄漏
- 數據傳輸
- 雜項
- 一提
- gogogoo
- 內容