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实例可互为父节点,从而可以组合成不同的应用形式;

更多文章如下:

【面向校招】全力备战2023Golang实习与校招

欢迎进群共同进步:
QQ群:1007576722

阅读世界,共赴山海 423全民读书节,邀你共读

以上是关于Golang面经ChannelContextGoroutine的主要内容,如果未能解决你的问题,请参考以下文章

Golang面经ChannelContextGoroutine

Golang面经ChannelContextGoroutine

golang 面经积累

第三十二期春招 Golang实习面经 七牛

Golang开发面经360(一轮游)

Golang开发面经360(一轮游)