Golang面经ChannelContextGoroutine
Posted 胡毛毛_三月
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang面经ChannelContextGoroutine相关的知识,希望对你有一定的参考价值。
目录
Channel
Channel是Go中的一个核心类型,你可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。
它的操作符是箭头 <- 。
channel一般用于协程之间的通信,channel也可以用于并发控制。比如主协程启动N个子协程,主协程等待所有子协程退出后再继续后续流程,这种场景下channel也可轻易实现。
ch <- v // 发送值v到Channel ch中
v := <-ch // 从Channel ch中接收数据,并将数据赋值给v
(箭头的指向就是数据的流向)
就像 map 和 slice 数据类型一样, channel必须先创建再使用:
ch := make(chan int, 100) // 使用make初始化Channel,并且可以设置容量
Channel读写特性(15字口诀)
首先,我们先复习一下Channel都有哪些特性?
- 给一个 nil channel 发送数据,造成永远阻塞
- 从一个 nil channel 接收数据,造成永远阻塞
- 给一个已经关闭的 channel 发送数据,引起 panic
- 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值
- 无缓冲的channel是同步的,而有缓冲的channel是非同步的
以上5个特性是死东西,也可以通过口诀来记忆:“空读写阻塞,写关闭异常,读关闭空零”。
使用channel来控制子协程的优点是实现简单,缺点是当需要大量创建协程时就需要有相同数量的channel,而且对于子协程继续派生出来的协程不方便控制。
channel线程安全
如果把线程安全定义为允许多个goroutine同时去读写,那么golang 的channel 是线程安全的。不需要在并发读写同一个channe时加锁。
Golang的Channel,发送一个数据到Channel 和 从Channel接收一个数据 都是 原子性的。
而且Go的设计思想就是:不要通过共享内存来通信,而是通过通信来共享内存,前者就是传统的加锁,后者就是Channel。
也就是说,设计Channel的主要目的就是在多任务间传递数据的,这当然是安全的
限制goroutine的数量
如何限制goroutine的数量
限制goroutine的数量
尝试 chan
使用带缓冲的channel,当channel数量达到限制的最大数量时,会阻塞。
以下是没有使用waitgroup的情况,最后结果是并不是所有的url都打印了,这当然不是想要的结果。
package main
import (
"fmt"
"sync"
"time"
)
var maxRoutineNum = 2
// routine限制最大数量
func main()
ch := make(chan int, maxRoutineNum)
var urls = []string
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
"http://www.golang.org/11",
"http://www.google.com/11",
"http://www.somestupidname.com/11",
"http://www.golang.org/22",
"http://www.google.com/22",
"http://www.somestupidname.com/22",
"http://www.golang.org/33",
"http://www.google.com/33",
"http://www.somestupidname.com/33",
for _, u := range urls
ch <- 1
go download1(u, ch)
// 模拟下载方法
func download1(url string, ch chan int)
fmt.Println( url)
// 休眠2秒模拟下载
time.Sleep(time.Second * 2)
// 下载完成从ch取出数据
x:= <- ch
fmt.Println("x:",x)
![image.png](https://img-blog.csdnimg.cn/img_convert/9b1fc25926ee08d436c3eddf4ff58ae0.png#clientId=ub6925226-3b16-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uf8774407&margin=[object Object]&name=image.png&originHeight=384&originWidth=338&originalType=url&ratio=1&rotation=0&showTitle=false&size=42294&status=done&style=none&taskId=ud5620e77-2852-49ae-80ad-78a1723b02a&title=)
但是新的问题出现了,因为并不是所有的goroutine都执行完了,在main函数退出之后,还有一些goroutine没有执行完就被强制结束了。这个时候我们就需要用到sync.WaitGroup。使用WaitGroup等待所有的goroutine退出。如下:
package main
import (
"fmt"
"sync"
"time"
)
var maxRoutineNum = 2
// routine限制最大数量
func main()
ch := make(chan int, maxRoutineNum)
var urls = []string
"http://www.golang.org/",
"http://www.google.com/",
"http://www.somestupidname.com/",
"http://www.golang.org/11",
"http://www.google.com/11",
"http://www.somestupidname.com/11",
"http://www.golang.org/22",
"http://www.google.com/22",
"http://www.somestupidname.com/22",
"http://www.golang.org/33",
"http://www.google.com/33",
"http://www.somestupidname.com/33",
wg := sync.WaitGroup
for _, u := range urls
wg.Add(1)
ch <- 1
go download(u, ch, &wg)
wg.Wait()
// 模拟下载方法
func download(url string, ch chan int , wg *sync.WaitGroup)
fmt.Println( url)
// 休眠2秒模拟下载
time.Sleep(time.Second * 2)
// 下载完成从ch取出数据
x:= <- ch
wg.Done()
fmt.Println("x:",x)
![image.png](https://img-blog.csdnimg.cn/img_convert/ce27e30981b12e054a5753f0694a5209.png#clientId=ub6925226-3b16-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ubb8e3dbc&margin=[object Object]&name=image.png&originHeight=431&originWidth=345&originalType=url&ratio=1&rotation=0&showTitle=false&size=45358&status=done&style=none&taskId=ua98e36d2-a756-4acf-9822-cff9b098037&title=)
Context
Golang context是Golang应用开发常用的并发控制技术,它与WaitGroup最大的不同点是context对于派生goroutine有更强的控制力,它可以控制多级的goroutine。
context翻译成中文是”上下文”,即它可以控制一组呈树状结构的goroutine,每个goroutine拥有相同的上下文。
![image.png](https://img-blog.csdnimg.cn/img_convert/2e039d6d06d050f8b1cb329656388dc3.png#clientId=uf27bda7a-6a20-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uf8e007b6&margin=[object Object]&name=image.png&originHeight=356&originWidth=590&originalType=url&ratio=1&rotation=0&showTitle=false&size=16834&status=done&style=none&taskId=ufbdc6d36-316b-4fb0-a560-4fbee82902b&title=)
接口定义
源码包中src/context/context.go:Context 定义了该接口:
type Context interface
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct
Err() error
Value(key interface) interface
基础的context接口只定义了4个方法,下面分别简要说明一下:
Deadline()
该方法返回一个deadline和标识是否已设置deadline的bool值,如果没有设置deadline,则ok == false,此时deadline为一个初始值的time.Time值
Done()
该方法返回一个channel,需要在select-case语句中使用,如”case <-context.Done():”。
当context关闭后,Done()返回一个被关闭的管道,关闭的管道仍然是可读的,据此goroutine可以收到关闭请求;
当context还未关闭时,Done()返回nil。
Err()
该方法描述context关闭的原因。关闭原因由context实现控制,不需要用户设置。比如Deadline context,关闭原因可能是因为deadline,也可能提前被主动关闭,那么关闭原因就会不同:
- 因deadline关闭:“context deadline exceeded”;
- 因主动关闭: “context canceled”。
当context关闭后,Err()返回context的关闭原因;
当context还未关闭时,Err()返回nil;
Value()
有一种context,它不是用于控制呈树状分布的goroutine,而是用于在树状分布的goroutine间传递信息。
Value()方法就是用于此种类型的context,该方法根据key值查询map中的value。具体使用后面示例说明。
- Context仅仅是一个接口定义,根据实现的不同,可以衍生出不同的context类型;
- cancelCtx实现了Context接口,通过WithCancel()创建cancelCtx实例;
- timerCtx实现了Context接口,通过WithDeadline()和WithTimeout()创建timerCtx实例;
- valueCtx实现了Context接口,通过WithValue()创建valueCtx实例;
- 三种context实例可互为父节点,从而可以组合成不同的应用形式;
更多文章如下:
欢迎进群共同进步:
QQ群:1007576722
以上是关于Golang面经ChannelContextGoroutine的主要内容,如果未能解决你的问题,请参考以下文章
Golang面经ChannelContextGoroutine