channel
无缓冲区的 channel
会阻塞,直到有 goroutine
读取数据
初始化 channel
,使用 make
函数:
-
有缓冲区:
ch := make(chan int, 10)
gofunc main() { ch := make(chan int, 10) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() ch <- 1 fmt.Println("goroutine1 done") // 会立即执行 }() time.Sleep(5 * time.Second) num := <-ch fmt.Println(num) wg.Wait() }
-
无缓冲区:
ch := make(chan int)
gofunc main() { ch := make(chan int) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() ch <- 1 fmt.Println("goroutine1 done") // 不会立即执行,等待 5 秒后在执行 }() time.Sleep(5 * time.Second) num := <-ch fmt.Println(num) wg.Wait() }
channel 循环
go
// channel 是引用类型,所以这里不需要传递指针
func worker(ch chan int, wg *sync.WaitGroup) {
defer wg.Done()
// channel 使用后需要关闭,否则是出现死锁
defer close(ch)
for i := 0; i < 50; i++ {
ch <- i * i
}
}
func main() {
ch := make(chan int, 100)
var wg sync.WaitGroup
wg.Add(1)
go worker(ch, &wg)
// 通过 range 循环 channel
for num := range ch {
fmt.Println(num)
}
wg.Wait()
}
channel 注意事项
未初始化的 channel
不能对其进行读写操作,否则会报错 all goroutines are asleep - deadlock!
go
var ch chan int
ch <- 1 // 写报错:all goroutines are asleep - deadlock!
n := <-ch // 读报错:all goroutines are asleep - deadlock!
fmt.Println(n)
关闭的 channel
不能再进行写操作,否则会报错 panic: send on closed channel
go
ch := make(chan int, 100)
close(ch)
ch <- 1 // channel 关闭后,在关闭的 channel 写数据,会报错:panic: send on closed channel
关闭的 channel
是可以读取的,读取到的数据是 channel
的零值
go
func main() {
ch := make(chan int, 100)
ch <- 1
close(ch)
// channel 关闭后,如果还有数据,可以继续读取
// 如果 ok 为 true,说明 channel 还没有关闭
n, ok := <-ch
fmt.Println(ok) // true
fmt.Println(n) // 1
}
go
func main() {
ch := make(chan int, 100)
close(ch)
// 如果 ok 为 false,说明 channel 已经关闭
n, ok := <-ch
fmt.Println(ok) // false
fmt.Println(n) // 1
}
channel 实现 WaitGroup
- 为什么用
defer
向ch
中写入空结构体- 保证
goroutine
无论是正常执行完还是发生panic
都能发送信号
- 保证
- 在读取
channel
数据时,如果channel
中还没有数据,channel
会被卡住
go
func main() {
ch := make(chan struct{})
for i := 0; i < 2; i++ {
go func(i int) {
// 用 defer 保证 goroutine 执行完后,wg.Done()
defer func() {
ch <- struct{}{}
}()
time.Sleep(2 * time.Second)
fmt.Println("i:", i)
}(i)
}
for i := 0; i < 2; i++ {
<-ch
}
fmt.Println("main end")
}
channel 限流
有缓冲的 channel
,如果缓冲区已满的话,会被阻塞,所以下面的代码会 4
个一组的打印
go
func main() {
ch := make(chan struct{}, 4)
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
ch <- struct{}{}
wg.Add(1)
go func(i int) {
defer func() {
wg.Done()
<-ch
}()
time.Sleep(2 * time.Second)
fmt.Println("i:", i) // 会 4 个一组的输出
}(i)
}
wg.Wait()
fmt.Println("main end")
}
不过这里要注意,缓冲区已满的话会阻塞,但如果缓冲区刚好够写入数据时,是不会阻塞的
所以下面的代码,最后 16
、17
、18
、19
是不会被打印的
go
func main() {
ch := make(chan struct{}, 4)
for i := 0; i < 20; i++ {
ch <- struct{}{}
go func(i int) {
defer func() {
<-ch
}()
time.Sleep(2 * time.Second)
fmt.Println("i:", i)
}(i)
}
fmt.Println("main end")
}
channel 实现锁
利用 channel
缓冲区已满需要等待的特性实现锁
-
缓冲区如果设置为
1
时,需要先写入数据,再读取数据,才能实现锁gofunc main() { // 缓冲区为 1,只能写入一个数据 ch := make(chan struct{}, 1) var wg sync.WaitGroup var counter int for i := 0; i < 20; i++ { wg.Add(1) go func() { defer wg.Done() ch <- struct{}{} // 先写入数据 counter++ <-ch // 再读取数据 }() } wg.Wait() fmt.Println(counter) }
-
缓冲区随便设置多少,但要预先写入一条数据,且只能写入一条数据
- 如果不预先写入数据,会出现死锁
- 如果预先写入多条数据,会出现数据竞争
- 因为预先写入一条数据时,当缓冲区满了的话,就会阻塞,然后一条一条的释放,达到锁的效果
gofunc main() { ch := make(chan struct{}, 100) var wg sync.WaitGroup var counter int ch <- struct{}{} // 预先写入一条数据 for i := 0; i < 200000; i++ { wg.Add(1) go func() { defer wg.Done() <-ch // 再读取数据 counter++ ch <- struct{}{} // 再写入数据,当缓冲区满的话,就无法写入数据了,需要等待前面的数据释放 }() } wg.Wait() fmt.Println(counter) }
select
for
循环中的 select
内部如果有 break
只能跳出 select
语句,不能跳出 for
循环,如果想跳出 for
循环,需要指定 label
go
func main() {
ch1 := make(chan int, 100)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
defer close(ch1)
for i := 0; i < 10; i++ {
ch1 <- i
time.Sleep(1 * time.Second)
}
}()
timeout := time.After(5 * time.Second)
loop:
for {
select {
case v, ok := <-ch1:
if !ok {
// 不指定 label,默认跳出 select
break loop
}
println(v)
case <-timeout:
println("timeout")
}
}
wg.Wait()
}
如果有多个 case
满足条件,select
会随机选择一个执行:
- 如果有
default
语句,当case
都不满足条件时,会执行default
语句 - 如果没有
default
语句,当case
都不满足条件时,select
会阻塞
下面例子是两个 goroutine
,一个每隔 1
秒向 ch1
写入数据,一个每隔 2
秒向 ch2
写入数据,select
会随机选择一个 case
执行,如果 8
秒内还没有执行完,会执行 timeout
语句,跳出 for
循环
go
func main() {
ch1 := make(chan int, 10)
ch2 := make(chan int, 10)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
defer close(ch1)
for i := 0; i < 10; i++ {
ch1 <- i
time.Sleep(1 * time.Second)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
defer close(ch2)
for i := 0; i < 10; i++ {
ch2 <- i
time.Sleep(2 * time.Second)
}
}()
timeout := time.After(8 * time.Second)
loop:
for {
select {
case v, ok := <-ch1:
if !ok {
ch1 = nil
break
}
fmt.Printf("ch1: %d\n", v)
case v, ok := <-ch2:
if !ok {
ch2 = nil
break
}
fmt.Printf("ch2: %d\n", v)
case <-timeout:
println("timeout")
break loop
}
if ch1 == nil && ch2 == nil {
println("break")
break
}
}
wg.Wait()
}
reflect
reflect.MakeSlice
MakeSlice
和 make([]int, 1)
效果差不多,不过 MakeSlice
创建的是 reflect.Value
类型的切片,代码如下:
go
sliceType := reflect.TypeOf([]int{})
// 或者
sliceType = reflect.SliceOf(reflect.TypeOf(int(0)))
slice := reflect.MakeSlice(sliceType, 10, 10)
设置值:
go
slice.Index(0).SetInt(int64(1))
追加值:
go
slice = reflect.Append(slice, reflect.ValueOf(2), reflect.ValueOf(3)) // 追加值
slice = reflect.AppendSlice(slice, slice) // 追加切片
转换成普通的 slice
:
go
ss := slice.Interface().([]int)
reflect.MakeMap
初始化 makeMap
go
mapType := reflect.TypeOf([]int{})
// 或者
mapType = reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(""))
m := reflect.MakeMap(reflect.TypeOf(map[string]string{}))
设置值:
go
m.SetMapIndex(reflect.ValueOf("key"), reflect.ValueOf("value"))
迭代:
go
iter := m.MapRange()
for iter.Next() {
fmt.Println(iter.Key(), iter.Value())
}
转换成普通的 map
:
go
mm := m.Interface().(map[string]string)
reflect.MakeFunc
go
intType := reflect.TypeOf(0)
funcType := reflect.FuncOf([]reflect.Type{intType, intType}, []reflect.Type{intType}, false)
// 或者
funcType := reflect.TypeOf(func(int, int) int { return 0 })
f := reflect.MakeFunc(funcType, func(args []reflect.Value) []reflect.Value {
return []reflect.Value{reflect.ValueOf(int(args[0].Int() + args[1].Int()))}
})
fmt.Println(f.Call([]reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)})[0].Int())