go 中 channel select reflect 使用

channel

无缓冲区的 channel 会阻塞,直到有 goroutine 读取数据

初始化 channel,使用 make 函数:

  • 有缓冲区:ch := make(chan int, 10)

    go 复制代码
    func 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)

    go 复制代码
    func 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

  1. 为什么用 deferch 中写入空结构体
    • 保证 goroutine 无论是正常执行完还是发生 panic 都能发送信号
  2. 在读取 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")
}

不过这里要注意,缓冲区已满的话会阻塞,但如果缓冲区刚好够写入数据时,是不会阻塞的

所以下面的代码,最后 16171819 是不会被打印的

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. 缓冲区如果设置为 1 时,需要先写入数据,再读取数据,才能实现锁

    go 复制代码
    func 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)
    }
  2. 缓冲区随便设置多少,但要预先写入一条数据,且只能写入一条数据

    • 如果不预先写入数据,会出现死锁
    • 如果预先写入多条数据,会出现数据竞争
    • 因为预先写入一条数据时,当缓冲区满了的话,就会阻塞,然后一条一条的释放,达到锁的效果
    go 复制代码
    func 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 实现 json 序列化

reflect.MakeSlice

MakeSlicemake([]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())
相关推荐
美美的海顿13 分钟前
spring boot 火车售票微信小程序LW
spring boot·后端·微信小程序·小程序·毕业设计
Q_192849990617 分钟前
基于Spring Boot的个性化推荐外卖点餐系统
java·spring boot·后端
brzhang39 分钟前
十年磨一剑:那些关于长期软件开发的思考,架构设计中如何做好技术选型
前端·后端·架构
机器视觉知识推荐、就业指导1 小时前
C++设计模式:组合模式(公司架构案例)
c++·后端·设计模式·组合模式
@菜鸟进阶记@1 小时前
SpringBoot核心:自动配置
java·spring boot·后端
汤姆yu1 小时前
基于springboot的健身俱乐部网站系统
java·spring boot·后端·健身房·俱乐部
方圆想当图灵2 小时前
由 Mybatis 源码畅谈软件设计(九):“能用就行” 其实远远不够
后端·代码规范
2401_846535952 小时前
Scala项目(图书管理系统)
开发语言·后端·scala
锅包肉的九珍2 小时前
Scala图书管理系统
开发语言·后端·scala
SomeB1oody2 小时前
【Rust自学】5.1. 定义并实例化struct
开发语言·后端·rust