经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
20个Golang片段让我不再健忘
来源:cnblogs  作者:京东云技术团队  时间:2023/6/8 9:27:47  对本文有异议

前言

本文使用代码片段的形式来解释在 go 语言开发中经常遇到的小功能点,由于本人主要使用 java 开发,因此会与其作比较,希望对大家有所帮助。

1. hello world

新手村的第一课,毋庸置疑。

  1. package main
  2. import "fmt"
  3. func main() {
  4. fmt.Printf("hello world")
  5. }

2. 隐形初始化

  1. package main
  2. import "fmt"
  3. func main() {
  4. load()
  5. }
  6. func load() {
  7. fmt.Printf("初始化..手动%s 不错\n", "1")
  8. }
  9. func init() {
  10. fmt.Printf("隐形初始化。。\n")
  11. }

在 go 中定义 init 函数,程序在运行时会自动执行。类似使 junit 的 [@before](https://my.oschina.net/u/3870904) 注解。

3. 多模块的访问

java 中 package 包的概念,go 是通过文件夹 + package 关键字来定义的。

一般而言,我们会通过go init来创建项目,生成的go.mod文件位于根目录。

常见的实践是,创建文件夹并且保持 package 名称与文件夹保持一致。这样 import 的永远是文件夹,遵循以上规则则意味着文件夹的名称即为模块名。

同一个 package 可以创建多个 .go 文件,虽然分布在不同的文件中。但是他们中的方法名称不能相同。需要注意,这里与 java中不同类中方法可以重名不同。

此外,也没有诸如private、protected、public等包访问权限关键字。只要定义的函数首字母为大写。则可以被外部成功调用。

来看一下示例:

  1. go-tour
  2. └── ch3
  3. ├── model
  4. └── test
  5. ├── testNest.go
  6. └── helper.go
  7. └── helper2.go
  8. └── main.go
  9. └── go.mod

此处,ch3、model、test 均为文件夹,也可以说是 packagehelper.go 位于 model 下,它的代码如下:

  1. package model
  2. import "fmt"
  3. var AppName = "bot"
  4. var appVersion = "1.0.0"
  5. func Say() {
  6. fmt.Printf("%s", "hello")
  7. }
  8. func init() {
  9. fmt.Printf("%s,%s", AppName, appVersion)
  10. }

再来看看 main.go

  1. package main
  2. import (
  3. "ch3/model"
  4. "ch3/model/test"
  5. )
  6. func main() {
  7. model.Say()
  8. }

显然它的调用是通过 packageName.MethodName() 来使用的。需要注意的是,一个 go.mod 下只能有一个 main 包。

4. 引用外部库

和 java 的 maven 类似,go 几经波折也提供了官方仓库。如下,通过 go get github.com/satori/go.uuid 命令即可安装 uuid 库,未指定版本,因此下载的为最新版本。

使用时是这样的:

  1. package main
  2. import (
  3. "fmt"
  4. uuid "github.com/satori/go.uuid"
  5. )
  6. func main() {
  7. uuid := uuid.NewV4()
  8. fmt.Printf("%s", uuid)
  9. }

5. 数组字典和循环

直接看代码就是了。

  1. package main
  2. import "fmt"
  3. var item []int
  4. var m = map[int]int{
  5. 100: 1000,
  6. }
  7. var m2 = make(map[int]int)
  8. func main() {
  9. for i := 0; i < 10; i++ {
  10. item = append(item, i)
  11. m[i] = i
  12. m2[i] = i
  13. }
  14. for i := range item {
  15. fmt.Printf("item vlaue=%d\n", i)
  16. }
  17. for key, value := range m {
  18. fmt.Printf("m:key=%d,value=%d\n", key, value)
  19. }
  20. for _, value := range m2 {
  21. fmt.Printf("m2:value=%d\n", value)
  22. }
  23. }
  • := 的形式只能在方法内
  • 全局的只能用 var x=..
  • map输出没有顺序

6. 结构体和JSON

go 中通过 struct 来定义结构体,你可以把它简单理解为对象。一般长这样。

  1. type App struct {
  2. AppName string
  3. AppVersion string `json:"app_version"`
  4. appAuthor string "pleuvoir"
  5. DefaultD string "default"
  6. }

我们经常在 java 程序中使用 fastjson 来输出 JSON字符串。 go 中自带了这样的类库。

  1. package main
  2. import (
  3. app2 "app/app" //可以定义别名
  4. "encoding/json"
  5. "fmt"
  6. )
  7. func main() {
  8. a := app2.App{}
  9. fmt.Printf("%s\n", a)
  10. app := app2.App{AppName: "bot", AppVersion: "1.0.1"}
  11. json, _ := json.Marshal(app) //转换为字符串
  12. fmt.Printf("json is %s\n", json)
  13. }
  • 结构体中 JSON 序列化不会转变大小写,可以指定它输出的 key名称通过 json:xxx 的描述标签。
  • 结构体中的默认值赋值了也不展示

7. 异常处理

作为一个有经验的程序员:),go 的异常处理涉及的很简单,也往往为人所诟病。比如满屏幕的 err 使用。

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. func _readFile() (int, error) {
  7. file, err := os.ReadFile("test.txt")
  8. if err != nil {
  9. fmt.Printf("error is = %s\n", err)
  10. return 0, err
  11. }
  12. fmt.Printf("file = %s \n", file)
  13. return len(file), err
  14. }
  15. func readFile() (int, error) {
  16. fileLength, err := _readFile()
  17. if err != nil {
  18. fmt.Printf("异常,存在错误 %s\n", err)
  19. }
  20. return fileLength, err
  21. }
  22. func main() {
  23. fileLength, _ := readFile()
  24. fmt.Printf("%d\n", fileLength)
  25. }

和 java 不同,它支持多返回值,为我们的使用带来了很多便利。如果不需要处理这个异常,可以使用 _ 忽略。

8. 异步

千呼万唤始出来,令人兴奋的异步。

  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "os"
  6. )
  7. func worker() {
  8. for i := 0; i < 10; i++ {
  9. fmt.Printf("i=%d\n", i)
  10. }
  11. }
  12. func main() {
  13. go worker()
  14. go worker()
  15. //阻塞 获取控制台的输出
  16. reader := bufio.NewReader(os.Stdin)
  17. read, err := reader.ReadBytes('\n') //注意是单引号 回车后结束控制台输出
  18. if err != nil {
  19. fmt.Printf("err is =%s\n", err)
  20. return
  21. }
  22. fmt.Printf("read is %s \n", read)
  23. }

如此的优雅,如此的简单。只需要一个关键字 go 便可以启动一个协程。我们在 java 中经常使用的是线程池,而在 go 中也存在协程池。据我观察,部分协程池 benchmark 的性能确实比官方语言关键字高很多。

9. 异步等待

这里就类似 java 中使用 countdownLatch 等关键字空值并发编程中程序的等待问题。

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. func upload(waitGroup *sync.WaitGroup) {
  8. for i := 0; i < 5; i++ {
  9. fmt.Printf("正在上传 i=%d \n", i)
  10. }
  11. time.Sleep(5 * time.Second)
  12. waitGroup.Done()
  13. }
  14. func saveToDb() {
  15. fmt.Printf("保存到数据库中\n")
  16. time.Sleep(3 * time.Second)
  17. }
  18. func main() {
  19. begin := time.Now()
  20. fmt.Printf("程序开始 %s \n", begin.Format(time.RFC850))
  21. waitGroup := sync.WaitGroup{}
  22. waitGroup.Add(1)
  23. go upload(&waitGroup)
  24. go saveToDb()
  25. waitGroup.Wait()
  26. fmt.Printf("程序结束 耗时 %d ms ", time.Now().UnixMilli()-begin.UnixMilli())
  27. }

sync 包类似于 J.U.C 包,里面可以找到很多并发编程的工具类。sync.WaitGroup 便可以简简单单认为是 countdownLatch 吧。也不能多次调用变为负数,否则会报错。

注意,这里需要传入指针,因为它不是一个引用类型。一定要通过指针传值,不然进程会进入死锁状态。

10. 管道

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. var ch = make(chan int)
  7. var sum = 0 //是线程安全的
  8. func consumer(wg *sync.WaitGroup) {
  9. for {
  10. select {
  11. case num, ok := <-ch:
  12. if !ok {
  13. wg.Done()
  14. return
  15. }
  16. sum = sum + num
  17. }
  18. }
  19. }
  20. func producer() {
  21. for i := 0; i < 10_0000; i++ {
  22. ch <- i
  23. }
  24. close(ch) //如果不关闭则会死锁
  25. }
  26. func main() {
  27. wg := sync.WaitGroup{}
  28. wg.Add(1)
  29. go producer()
  30. go consumer(&wg)
  31. wg.Wait()
  32. fmt.Printf("sum = %d \n", sum)
  33. }

这里演示的是什么呢?管道类似一个队列,进行线程间数据的传递。当关闭时消费端也退出,如果没关闭管道,运行时会报死锁。可以看出全局变量在线程间是安全的。

可以衍生出一种固定写法:

  1. //固定写法
  2. func consumer(wg *sync.WaitGroup) {
  3. for {
  4. select {
  5. case num, ok := <-ch:
  6. if !ok {
  7. wg.Done()
  8. return
  9. }
  10. sum = sum + num
  11. }
  12. }
  13. }

11. 接口

  1. package main
  2. import "fmt"
  3. type Person interface {
  4. Say()
  5. SetName(name string)
  6. }
  7. type ZhangSan struct {
  8. Value string
  9. }
  10. func (z *ZhangSan) Say() {
  11. fmt.Printf("name=%s", z.Value)
  12. }
  13. func (z *ZhangSan) SetName(name string) {
  14. z.Value = name + ":hehe"
  15. }
  16. func main() {
  17. zhangSan := ZhangSan{}
  18. zhangSan.SetName("pleuvoir")
  19. zhangSan.Say()
  20. }

如上的程序演示了接口的使用。

  • go的接口没有强依赖
  • 通过结构体 + 方法的形式实现,注意方法传入的可以是引用也可以是值

12. 锁

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. type Number struct {
  7. Value int
  8. mutex sync.Mutex //加锁
  9. }
  10. func (receiver *Number) Add() {
  11. receiver.mutex.Lock()
  12. defer receiver.mutex.Unlock() //退出时会执行
  13. receiver.Value = receiver.Value + 1
  14. //fmt.Printf("add\n")
  15. }
  16. func (receiver *Number) Get() int {
  17. receiver.mutex.Lock()
  18. defer receiver.mutex.Unlock()
  19. return receiver.Value
  20. }
  21. func main() {
  22. number := Number{Value: 0}
  23. wg := sync.WaitGroup{}
  24. n := 100_0000
  25. wg.Add(n)
  26. for i := 0; i < n; i++ {
  27. go func(wg *sync.WaitGroup) {
  28. number.Add()
  29. wg.Done()
  30. }(&wg)
  31. }
  32. wg.Wait()
  33. fmt.Printf("count=%d", number.Get())
  34. }

这里是什么?显然就像是显示锁的 ReentrantLock 的使用,相信大家都能看懂。这里出现了新关键字 defer,我暂且是理解为 finally。不知道你怎么看?

13. 读写配置文件

这也是一个很常规的功能,看看怎么实现。

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. )
  7. type Preferences struct {
  8. Name string `json:"name"`
  9. Version float64 `json:"version"`
  10. }
  11. const configPath = "config.json"
  12. func main() {
  13. preferences := Preferences{Name: "app", Version: 100.01}
  14. marshal, err := json.Marshal(preferences)
  15. err = os.WriteFile(configPath, marshal, 777)
  16. if err != nil {
  17. fmt.Printf("写入配置文件错误,%s\n", err)
  18. return
  19. }
  20. //读取配置文件
  21. file, err := os.ReadFile(configPath)
  22. if err != nil {
  23. fmt.Printf("读取文件错误,%s\n", err)
  24. return
  25. }
  26. fmt.Printf("%s\n", file) //{"name":"app","version":100.01}
  27. //构建一个对象用来序列化
  28. readConfig := Preferences{}
  29. //反序列化
  30. err = json.Unmarshal(file, &readConfig)
  31. if err != nil {
  32. fmt.Printf("配置文件转换为JSON错误,%s\n", err)
  33. }
  34. fmt.Printf("%v", readConfig) //{app 100.01}

这里挺没意思的,写入 JSON 字符串,然后读取回来在加载到内存中。不过,简单的示例也够说明问题了。

14. 宕机处理

这是类似于一种最上层异常捕获的机制,在程序的入口处捕获所有的异常。

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func worker() {
  7. //defer func() { //不能写在主函数,最外层catch没啥用
  8. // if err := recover(); err != nil {
  9. // fmt.Printf("%s", err)
  10. // }
  11. //}()
  12. defer recovery()
  13. panic("严重错误")
  14. }
  15. func recovery() {
  16. if err := recover(); err != nil {
  17. fmt.Printf("死机了。%s\n", err)
  18. }
  19. }
  20. func main() {
  21. for true {
  22. worker()
  23. time.Sleep(1 * time.Second)
  24. }
  25. }

注释写的很清楚,聪明的你一看就懂。

15. 单元测试

与 java 不同,go 建议单元测试文件尽可能的离源代码文件近一些。比如这样:

  1. go-tour
  2. └── main.go
  3. └── main_test.go

并且它的命名也是这样简单粗暴:

  1. package main
  2. import (
  3. "testing"
  4. )
  5. func TestInit(t *testing.T) {
  6. t.Log("heh")
  7. helper := PersonHelper{}
  8. helper.init("pleuvoir")
  9. t.Log(helper.Name)
  10. }

以大写的 Test 开头,文件名称以 _test 结尾,很清爽的感觉。

16. 启动传参

这也是一个很常用的知识点。这里有两种方式:

  • 直接传
  • 使用 flag
  1. package main
  2. import (
  3. "encoding/json"
  4. "flag"
  5. "fmt"
  6. "os"
  7. )
  8. func main() {
  9. //第一种方式
  10. args := os.Args
  11. for i, arg := range args {
  12. println(i, arg)
  13. }
  14. //第二种方式
  15. config := struct {
  16. Debug bool
  17. Port int
  18. }{}
  19. flag.BoolVar(&config.Debug, "debug", true, "是否开启debug模式")
  20. flag.IntVar(&config.Port, "port", 80, "端口")
  21. flag.Parse()
  22. json, _ := json.Marshal(config)
  23. fmt.Printf("json is %s\n", json)
  24. }

我建议使用第二种,更便捷自带类型转换,还可以给默认值,非常好。

17. 优雅退出

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "os/signal"
  6. "syscall"
  7. )
  8. func quit() {
  9. println("执行一些清理工作。。")
  10. }
  11. //正常的退出
  12. //终端 CTRL+C退出
  13. //异常退出
  14. func main() {
  15. defer quit()
  16. println("进来了")
  17. //读取信号,没有一直会阻塞住
  18. exitChan := make(chan os.Signal)
  19. //监听信号
  20. signals := make(chan os.Signal)
  21. signal.Notify(signals, syscall.SIGINT, syscall.SIGQUIT)
  22. go func() {
  23. //有可能一次接收到多个
  24. for s := range signals {
  25. switch s {
  26. case syscall.SIGINT, syscall.SIGQUIT:
  27. println("\n监听到操作系统信号。。")
  28. quit() //如果监听到这个信号没处理,那么程序就不会退出了
  29. if i, ok := s.(syscall.Signal); ok {
  30. value := int(i)
  31. fmt.Printf("是信号类型,准备退出 %d", value)
  32. } else {
  33. println("不知道是啥,0退出")
  34. os.Exit(0)
  35. }
  36. // os.Exit(value)
  37. exitChan <- s
  38. }
  39. }
  40. }()
  41. println("\n程序在这里被阻塞了。")
  42. <-exitChan
  43. //panic("heh")
  44. println("\n阻塞被终止了。")
  45. }

这其实是在监听操作系统的信号,java 中也有类似的回调的接口(我忘了名字)。

18. 反射

作为一门高级语言,反射肯定是有的。还是使用 reflect 包。

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type Person struct {
  7. Name string `json:"name"`
  8. }
  9. func (p *Person) SetName(name string) {
  10. p.Name = name
  11. }
  12. func (p *Person) GetName() (string, string) {
  13. return p.Name, "1.0.1"
  14. }
  15. func worker1() {
  16. p := Person{}
  17. p.SetName("pleuvoir")
  18. name, _ := p.GetName()
  19. fmt.Printf(name)
  20. }
  21. // 获取方法
  22. func worker2() {
  23. p := Person{}
  24. rv := reflect.ValueOf(&p)
  25. value := []reflect.Value{reflect.ValueOf("peluvoir")}
  26. rv.MethodByName("SetName").Call(value)
  27. values := rv.MethodByName("GetName").Call(nil)
  28. for i, v := range values {
  29. fmt.Printf("\ni=%d,value=%s\n", i, v)
  30. }
  31. }
  32. func worker3() {
  33. s := Person{}
  34. rt := reflect.TypeOf(s)
  35. if field, ok := rt.FieldByName("Name"); ok {
  36. tag := field.Tag.Get("json")
  37. fmt.Printf("tag is %s \n", tag)
  38. }
  39. }
  40. func main() {
  41. //正常获取
  42. worker1()
  43. //获取方法
  44. worker2()
  45. //获取标签
  46. worker3()
  47. }

没什么好说的,写代码全靠猜。

19. atomic

类似 java 中的 atomic 原子变量。

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "sync/atomic"
  6. )
  7. func main() {
  8. workers := 1000
  9. wg := sync.WaitGroup{}
  10. wg.Add(workers)
  11. for i := 0; i < workers; i++ {
  12. go worker2(&wg)
  13. }
  14. wg.Wait()
  15. fmt.Printf("count = %d", count)
  16. }
  17. var count int64 = 0
  18. func worker1(wg *sync.WaitGroup) {
  19. count++
  20. wg.Done()
  21. }
  22. func worker2(wg *sync.WaitGroup) {
  23. atomic.AddInt64(&count, 1) //特别简单
  24. wg.Done()
  25. }

真的是特别简单。

20. 线程安全的Map

类似于ConcurrentHashMap,与普通的 api 有所不同。

  1. var sessions = sync.Map{}
  2. sessions.Store(uuid, uuid)
  3. load, ok := sessions.Load(value.Token)
  4. if ok {
  5. // 做你想做的事情
  6. }

21. return func

这里就是函数式变成的例子了。函数是一等公民可以作为参数随意传递。java 什么时候能支持呢?

  1. package main
  2. import "fmt"
  3. func main() {
  4. engine := Engine{}
  5. engine.Function = regular()
  6. function := engine.Function
  7. for i := 0; i < 3; i++ {
  8. s := function("pleuvoir")
  9. fmt.Printf("s is %s\n", s)
  10. }
  11. }
  12. type Engine struct {
  13. Function func(name string) string
  14. }
  15. func regular() (ret func(name string) string) {
  16. fmt.Printf("初始化一些东西。\n")
  17. return func(name string) string {
  18. fmt.Printf("我是worker。name is %s\n", name)
  19. return "我是匿名函数的返回值"
  20. }
  21. }

比如这里,如果要初始化日志什么。最后需要让框架在哪里打印日志,就需要将这个初始化的日志实例传递过去。总而言之,言而总之。会需要让代码各种传递。

这种方式在于第一次调用的时候会执行上面的代码片段,后面只是保存了这个函数的句柄,然后可以一直调用这个匿名函数。

22. context

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. )
  7. func main() {
  8. worker1()
  9. }
  10. func worker1() {
  11. //总共2秒超时
  12. value := context.WithValue(context.Background(), "token", "pleuvoir")
  13. timeout, cancelFunc := context.WithTimeout(value, 5*time.Second)
  14. defer cancelFunc()
  15. //模拟任务
  16. fmt.Println("开始任务")
  17. deep := 10
  18. go handler(timeout, deep)
  19. fmt.Println("开始阻塞", time.Now())
  20. //等待主线程超时,阻塞操作
  21. select {
  22. case <-timeout.Done():
  23. fmt.Println("阻塞结束", timeout.Err(), time.Now())
  24. }
  25. }
  26. // 模拟任务处理,循环下载图片等
  27. func handler(timeout context.Context, deep int) {
  28. if deep > 0 {
  29. fmt.Printf("[begin]token is %s %s deep=%d\n", timeout.Value("token"), time.Now(), deep)
  30. time.Sleep(1 * time.Second)
  31. go handler(timeout, deep-1)
  32. }
  33. //下面的哪个先返回 先执行哪个
  34. //如果整体超时 或者 当前方法超过2秒 就结束
  35. select {
  36. //等待超时会返回
  37. case <-timeout.Done():
  38. fmt.Println("超时了。", timeout.Err())
  39. //等待这么久 然后会返回 这个函数可不是比较时间,这里其实是在模拟处理任务,固定执行一秒 和休息一秒效果一样
  40. //但是休息一秒的话就不会实时返回了,所以这里实际应用可以是一个带超时的回调?
  41. case <-time.After(time.Second):
  42. fmt.Printf("[ end ]执行完成耗时一秒 %s %d\n", time.Now(), deep)
  43. }
  44. }

作用:在不同的协程中传递上下文。

  • 传值 类似于threadLocal
  • 可以使用超时机制,无论往下传递了多少协程,只要最上层时间到了 后面的都不执行
  • 俄罗斯套娃一次一层包装

23. 字符串处理

这是最高频率的操作了,使用任何语言都无法错过。

  1. package main
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. func main() {
  7. str := " pleuvoir "
  8. trimSpace := strings.TrimSpace(str)
  9. fmt.Printf("去除空格 %s\n", trimSpace)
  10. subString := trimSpace[4:len(trimSpace)]
  11. fmt.Printf("subString after is %s\n", subString)
  12. prefix := strings.HasPrefix(subString, "vo")
  13. fmt.Printf("是否有前缀 vo : %v\n", prefix)
  14. suffix := strings.HasSuffix(subString, "ir")
  15. fmt.Printf("是否有后缀 ir : %v\n", suffix)
  16. builder := strings.Builder{}
  17. builder.WriteString("hello")
  18. builder.WriteString(" ")
  19. builder.WriteString("world")
  20. fmt.Printf("stringBuilder append is %s\n", builder.String())
  21. eles := []string{"1", "2"}
  22. join := strings.Join(eles, "@")
  23. fmt.Printf("join after is %s\n", join)
  24. //拼接格式化字符串,并且能返回
  25. sprintf := fmt.Sprintf("%s@%s", "1", "20")
  26. fmt.Printf("Sprintf after is %s\n", sprintf)
  27. //打印一个对象 比较清晰的方式
  28. person := struct {
  29. Name string
  30. Age int
  31. }{"pleuvoir", 18}
  32. fmt.Printf("%v", person) // 输出 {Name:pleuvoir Age:18}
  33. }

主要是使用 fmt 包。

24. 任务投递

如果说使用 go 最激动人心的是什么?是大量的协程。如果在下载任务中,我们可以启动很多协程进行分片下载。如下,即展示使用多路复用高速下载。

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. func main() {
  8. chunks := 10 //文件分成n份
  9. workers := 5 //个线程处理
  10. wg := sync.WaitGroup{}
  11. wg.Add(chunks)
  12. jobs := make(chan int, chunks) //带缓冲的管道 等于任务数
  13. for i := 0; i < workers; i++ {
  14. go handler1(i, jobs, &wg)
  15. }
  16. //将任务全部投递给worker
  17. scheduler(jobs, chunks)
  18. wg.Wait()
  19. fmt.Println("download finished .")
  20. }
  21. // 分成 chunks 份任务 里分发
  22. // 将 n 份下载任务都到管道中去,这里管道数量等于 任务数量n 管道不会阻塞
  23. func scheduler(jobs chan int, chunks int) {
  24. for i := 0; i < chunks; i++ {
  25. //time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
  26. jobs <- i
  27. }
  28. }
  29. // 写法2
  30. // 注意这里的是直接接受管道,这也是一种固定写法,下面的 range jobs 可以认为是阻塞去抢这个任务,多个线程都在抢任务
  31. func handler2(workerId int, jobs <-chan int, wg *sync.WaitGroup) {
  32. for job := range jobs {
  33. // fmt.Printf("workerId[%d] job[%d] start download .\n", workerId, job)
  34. time.Sleep(1 * time.Second)
  35. fmt.Printf("workerId[%d] job[%d] download ok.\n", workerId, job)
  36. wg.Done() //这里不要break,这样执行完当前的线程就能继续抢了
  37. }
  38. }
  39. // 写法1,select case 多路复用
  40. func handler1(workerId int, jobs chan int, wg *sync.WaitGroup) {
  41. for {
  42. select {
  43. case job, _ := <-jobs:
  44. // fmt.Printf("workerId[%d] job[%d] start download .\n", workerId, job)
  45. time.Sleep(3 * time.Second)
  46. fmt.Printf("workerId[%d] job[%d] download ok.\n", workerId, job)
  47. wg.Done() //这里不要break,这样执行完当前的线程就能继续抢了
  48. }
  49. }
  50. }

后语

以上都是一个新手 Gopher 的经验总结,文中难免有错误,恳请指正。

作者:京东零售 付伟

来源:京东与开发者社区

原文链接:https://www.cnblogs.com/jingdongkeji/p/17461377.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号