Please enable Javascript to view the contents

Golang-中级-channel注意

 ·  ☕ 4 分钟

1. 知识点

简单介绍channel

  1. 对nil的channel读写会如何?

答: 读写都会造成阻塞

  1. 对已关闭的channel操作会有什么特点?

答:读,无缓存值 → 读出0值; 读,有值 → 正常读; → panic; 关闭 → panic;

  1. channel的原理、概念、底层实现?

答:通过makechan创建的hchan 分为有缓冲和无缓冲的通道

  1. 有缓冲与无缓冲的区别
    1、无缓冲的管道是同步的,必须使用协程接收,否则会导致死锁;
    2、有缓冲是异步的,但是如果缓冲区已满,发送消息会阻塞,如果缓冲区为空,接收消息会阻塞;

  2. 如何判断一个channel已关闭
    1、往一个已经关闭的管道写数据,panic,路子比较野
    2、使用_, ok := ch结合select来实现,ok如下情况,
    true:读到数据,并且通道没有关闭。
    false:通道关闭,无数据读到

2. channel的应用场景

channel适用于数据在多个协程中流动的场景,有很多实际应用:

  1. 任务定时
    比如超时处理:
    select {
    case <-time.After(time.Second):

定时任务
select {
case <- time.Tick(time.Second)

  1. 解耦生产者和消费者
    可以将生产者和消费者解耦出来,生产者只需要往channel发送数据,而消费者只管从channel中获取数据。

  2. 控制并发数
    以爬虫为例,比如需要爬取1w条数据,需要并发爬取以提高效率,但并发量又不过过大,可以通过channel来控制并发规模,比如同时支持5个并发任务:ch := make(chan int, 5)for _, url := range urls {go func() {ch <- 1worker(url)<- ch}}

  3. 协程之间的通信. 通过channel传递数据,实现通信

  4. 协程之间的同步. 通过控制channel读写数据的时序,实现不同协程之间的同步

  5. 实现工作池. 通过channel实现对工作池任务的分配和处理

  6. 实现信号量. 设置1大小的缓冲区,模拟加锁和解锁

  7. 实现超时机制. channel结合select实现超时任务.

go里面只有值拷贝

  1. 为什么不要使用大量的协程?

1、过多会占有太多的cpu资源和内存,可能使系统资源耗尽
2、因为GMP,M和P都是有数量限制的,如果调度队列过长,也会影响性能;
3、频繁GC也会影响性能;

使用goroutine可以帮助提高程序的并发性和性能,但是过度使用goroutine会带来一些问题,例如:
内存占用增加,因为每个goroutine都需要占用一定的内存
过多的goroutine会导致CPU上下文切换频繁,影响程序性能
如果goroutine没有正确的管理,可能会导致资源泄漏或死锁为了优化这些问题,可以考虑以下方法:
确定适当的goroutine数量,避免过度使用goroutine。
使用有限的goroutine池,以限制goroutine的总数,并避免内存占用过多。
优化goroutine的调度,以减少CPU上下文切换的次数。
使用通道和其他同步原语来避免竞争条件和死锁。
https://www.jianshu.com/p/1a50330adf1b

  1. 读写锁如何实现的?

读写锁的规则:
RWMutex在读锁占用的是时候,会阻塞写,不会阻塞读;
RWMutex在写锁占用的时候,无论是读还是写都会被阻塞;
读写锁的底层实现也是基于互斥锁来实现的。

如果没有writer请求进来,则每个reader开始后只是将readerCount增1,完成后将readerCount减1,整个过程不阻塞,这样就做到“并发读操作之间不互斥”;
当有writer请求进来时首先通过互斥锁阻塞住新来的writer,做到“并发写操作之间互斥”;
然后将readerCount改成一个很小的值,从而阻塞住新来的reader;
记录writer进来之前未完成的reader数量,等待它们都完成后再唤醒writer;这样就做到了“并发读操作和写操作互斥”;
writer结束后将readerCount置回原来的值,保证新的reader不会被阻塞,然后唤醒之前等待的reader,再将互斥锁释放,使后续writer不会被阻塞。

主要是两个计数器,count正常读请求就是+1-1,有写锁的话就会变为一个很小的负数防止后来的读请求执行,然后用readerWaiter记录之前的读请求数量,等待执行完,唤醒写请求,然后再解写锁给还原回去,再继续执行之后的读请求等

  1. 深拷贝与浅拷贝
    深拷贝: 相当于重新克隆了一份
    浅拷贝: 相当拷贝了一份引用,地址还是指向原有的地址,修改会对原有值有影响

深拷贝 = 值拷贝 不互相影响
浅拷贝 = 引用拷贝 互相影响

  1. make和new的区别

make只能用来创建slice、map、channel, 而new可以用来创建任何类型

make返回一个有初始值的实例new创建一个没有任何数据的类型,返回指针

  1. 数组如何实现用下标访问任一元素?
    1、数组是线性表;
    2、数组的元素在内存中存放是连续的,利用下班访问任意元素可以通过寻址公式:
    a[i]_address = base_address + i* data_type_size
    其中base_address表示首地址,data_type_size 表示数组中每个元素的大小
    一般来说可以用首地址+数据类型大小*偏移量计算得到
分享

Hex
作者
Hex
CloudNative Developer

目录