> # 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
- 相关阅读
- 切片 slice
- 集合 map
- 协程 goroutine
- 通道 channel
- 相关阅读 s
- pyTorch
- defer
- 内存泄漏