😥 整理不易,此资源只针对正式星主开放,
还请入驻星球后再来观看。

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • Go真实面试题汇总系列

    • Go基础篇
  • 宝典内容

    • 20. 2个协程交替打印字母和数字
    • 21. goroutine与线程的区别?
    • 27. 为什么不要大量使用goroutine
    • 38. 协程goroutine
    • 41. go 中用 for 遍历多次执行 goroutine会存在什么问题
    • 49. 问等待所有goroutine结束,怎么做?
    • 56. goroutine 为什么轻量
    • 86. 用Channel和两个协程实现数组相加
    • 98. go协程的实现方式
    • 106. 并行goroutine如何实现
    • 109. goroutine和线程的区别,为什么说goroutine轻量
    • 117. 父 goroutine 退出,如何使得子 goroutine 也退出?
    • 125. 主协程如何等待其余协程完再操作
    • 141. 为什么不要频繁创建和停止goroutine
    • 146. 如何拿到多个goroutine的返回值,如何区别他们
    • 151.golang goroutine的工作原理以及他们怎么进行数据交互的
    • 157. 一个线程打印奇数一个线程打印偶数 交替打印
    • 160. channel主要做什么事情
    • 167. golang 协程机制
    • 168. 协程的栈空间大小有限制吗?会主动扩展吗?
    • 169. 用go实现一个协程池,大概用什么实现
    • 170. go里面为什么需要多协程?
    • 171. goroutine为什么会存在,为什么不使用线程?
    • 173. go协程线程进程区别
    • 176. go协程相比其它协程库区别在哪?
    • 184. 编程go协程交叉顺序打印数组
    • 185. go协程通信
    • 203. 一个goroutine sleep了,操作系统是怎么唤醒的
    • 222. Go的协程可以不可以自己让出cpu
    • 223. Go的协程可以只挂在一个线程上面吗
    • 239. 用go撸一个生产者消费型,用channel通信,怎么友好的关闭chan?
    • 240. goroutine调度源码
    • 248. go实现协程池
    • 249. 两个协程交替打印1到20
    • 251. goroutine 和 kernel thread 之间是什么关系?
    • 254. 用过go,那么进程,协程,线程各自的优缺点
    • 264. Go的多线程
    • 268. 进程和协程
    • 269. 如何解决孤儿进程的出现
    • 274. goroutine为什么比线程开销小,实现原理
    • 277. 协程实现顺序打印123
    • 286. goroutine的调度是出现在什么情况下,调度时做了什么
    • 297. golang有什么提高性能的设计, 重点说说goroutine
    • 298. 进程、线程和协程和通信方式
    • 300. Goroutine 数量是越多越好吗?
    • 308. go协程的简单用法
    • 314. 为什么用户级别的线程 goroutine 比操作系统线程更轻量级?
    • 341. 协程怎么停顿?
    • 367. go 如何关闭goroutine
    • 373. Go 语言协程怎么跑的
    • 375. Go创建协程的过程
    • 376. 协程共享哪些资源?
    • 385. go中协程是如何实现的
    • 386. 协程中参数直接使用,和传参的区别是什么,为什么会造成这种结果
    • 389. 是否写过go语言多协程内容
    • 394. 开俩个协程,一个协程生产数据,另一个协程对数据进行处理,处理完后再把数据发回去,使用管道如何实现?
    • 406. goroutine泄露
    • 407. 如何停止一个goroutine
    • 410. 查看goroutine (579)
    • 420. 用go协程的时候也是要走IO的,go是如何处理的?
    • 444. 有没有了解过goroutine的底层数据结构, 为什么协程比线程轻量且快
    • 461. 如何限制 goroutine 并发数量 (channel 或 WaitGroup)
    • 465. Go里面一个协程能保证绑定在一个内核线程上面的。

😥 整理不易,此资源只针对正式星主开放,
还请入驻星球后再来观看。

407. 如何停止一个goroutine


企业题库解析小组

题目序号:545 题目来源:早安科技 频次:

一、使用channel进行控制

Go语言有一个著名的设计哲学:Do not communicate by sharing memory; instead, share memory by communicating.——通过通信共享内存,而不是通过共享内存来进行通信。Go语言中实现goroutine之间通信的机制就是channel。因此我们可以使用channel来给goroutine发送消息来变更goroutine的行为。下面是使用channel控制的几种方式。

1.1 for-range结构

for-rang从channel上接收值,直到channel关闭,该结构在Go并发编程中很常用,这对于从单一通道上获取数据去执行某些任务是十分方便的。示例如下

package main

import (
  "fmt"
  "sync"
)

var wg sync.WaitGroup

func worker(ch chan int) {
  defer wg.Done()
  for value := range ch {
    fmt.Println(value) // do something
  }
}

func main() {
  ch := make(chan int)

  wg.Add(1)
  go worker(ch)

  for i := 0; i < 3; i++ {
    ch <- i
  }

  close(ch)
  wg.Wait()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

1.2 for-select结构

当channel比较多时,for-range结构借不是很方便了。Go语言提供了另外一种和channel相关的语法: select。select能够让goroutine在多个通信操作上等待(可以理解为监听多个channel)。由于这个特性,for-select结构在Go并发编程中使用的频率很高。我在使用Go的开发中,这是我用的最多的一种组合形式:

for {
    select {
    }
}
1
2
3
4

for-select的使用十分灵活,这里我举两个例子

1.2.1 指定一个退出通道

对于for-select结构,一般我会定义一个特定的退出通道,用于接收退出的信号,如quit。退出通道的使用也分两情况,下面看两个示例。

向退出通道发送退出信号

package main

import (
  "fmt"
  "sync"
  "time"
)

var wg sync.WaitGroup

func worker(in, quit <-chan int) {
  defer wg.Done()
  for {
    select {
    case <-quit:
      fmt.Println("收到退出信号")
      return // 必须return,否则goroutine是不会结束的
    case v := <-in:
      fmt.Println(v)
    }
  }
}

func main() {
  quit := make(chan int) // 退出通道
  in := make(chan int)

  wg.Add(1)
  go worker(in, quit)

  for i := 0; i < 3; i++ {
    in <- i
    time.Sleep(1 * time.Second)
  }
  quit <- 1  // 向quit通道发送退出信号
  wg.Wait()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

关闭退出通道 上面这个例子中,如果启动了100个groutine,那么我们就需要向quit通道中发送100次数据,这就很麻烦。怎么办呢?很简单,关闭channel,这样所有监听quit channel的goroutine就都会收到关闭信号。上面的代码只要做一个很小的替换就能工作:

// wg.Add(1)
wg.Add(100)  //前提是你真的有100个goroutine

// quit <- 1 替换为下面的代码
close(quit)
1
2
3
4
5

1.2.2 多个channel都关闭才能退出

上面讲了定义一个特定的退出通道的方法。这里再讲另一个场景,如果select上监听了多个通道,需要所有的通道都关闭后才能结束goroutine, 这种要如何处理呢?

这里就利用select的一个特性,select不会在nil的通道上进行等待,因此将channel赋值为nil即可。此外,还需要利用channel的ok值。

package main

import (
  "fmt"
  "sync"
  "time"
)

var wg sync.WaitGroup

func worker(in1, in2 <-chan int) {
  defer wg.Done()
  for {
    select {
    case v, ok := <-in1:
      if !ok {
        fmt.Println("收到退出信号")
        in1 = nil
      }
      // do something
      fmt.Println(v)
    case v, ok := <-in2:
      if !ok {
        fmt.Println("收到退出信号")
        in2 = nil
      }
      // do something
      fmt.Println(v)
    }

      // select已经结束,我们需要判断两个通道的状态
    // 都为nil则结束当前goroutine
    if in1 == nil && in2 == nil {
      return
    }
  }
}

func main() {
  in1 := make(chan int) // 退出通道,接收
  in2 := make(chan int)

  wg.Add(2)

  go worker(in1, in2)
  go worker(in2, in2)

  for i := 0; i < 3; i++ {
    in1 <- i
    time.Sleep(1 * time.Second)
    in2 <- i
  }

  close(in1)
  close(in2)
  wg.Wait()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

二、使用context包

context包是官方提供的一个用于控制多个goroutine写作的包,篇幅受限,这里只举一个例子,这个例子说明了2个问题:

使用context的cancel信号,可以终止goroutine的运行 context是可以向下传递的

package  main

import (
  "context"
  "fmt"
  "sync"
)

var wg sync.WaitGroup

func gen(ctx context.Context) <-chan int {
  // 创建子context
  subCtx, _ := context.WithCancel(ctx)
  go sub(subCtx)  // 这里使用ctx,也能给goroutine通知

  dst := make(chan int)
  n := 1
  go func() {
    defer wg.Done()
    for {
      select {
      case <-ctx.Done():
        fmt.Println("end")
        return // return,防止goroutine泄露
      case dst <- n:
        n++
      }
    }
  }()
  return dst
}

func sub(ctx context.Context) {
  defer wg.Done()

  for {
    select {
    case <-ctx.Done():
      fmt.Println("end too")
      return // returning not to leak the goroutine
    default:
      fmt.Println("test")
    }
  }
}

func main() {
  wg.Add(2)

  ctx, cancel := context.WithCancel(context.Background())
  for n := range gen(ctx) {
    fmt.Println(n)
    if n == 5 {
      break
    }
  }
  
  cancel()
  wg.Wait()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

三、总结

在Go语言的并发编程中,goroutine的启动十分方便,但是goroutine的管理是需要自己去编程实现的。尤其是在多个goroutine协作时,更需要小心谨慎处理,否则程序会有意想不到的bug。

本文主要描述了如何实现从外部主动关闭goroutine的2种方式:

  • channel
  • context

主动关闭goroutine除了实现特定功能外,还能提升程序性能。goroutine由于某种原因阻塞,不能继续运行,此时程序应该干预,将goroutine结束,而不是让他一直阻塞,如果此类goroutine很多,会耗费更多的资源。因此,有效的管理goroutine是十分有必要的。