《Go程序设计语言》- 第8章:goroutine和通道

前言

本专栏是笔者在学习《Go程序设计语言》这本书时,对每个章节认为较为重要容易忘记👻)的知识点记录的笔记,其中也会有少量的思考👀, 现整理成博客分享出来。

如果对专栏感兴趣,跑过去看一眼,书中的每一章都有:《Go程序设计语言》笔记

❗️注意❗️:本专栏不是详细的知识讲解,只是碎片的知识条目,或可作为Go知识点查漏补缺的小工具~

章节开篇知识

  1. goroutine和通道channel支持通信顺序进程,这是一种并发的模式;

goroutine

  1. 每一个并发执行的活动称为goroutine;它和线程在数量上有非常大的差别;
  2. 当一个程序启动时,只有一个goroutine来调用main函数,称它为主goroutine
  3. 新的goroutine通过go funcName()语句进行创建,将普通方法的调用创建为goroutine
  4. main函数返回时,所有goroutine都将被暴力终结;
  5. 没有程序化的方法让一个goroutine来停止另一个,但可以使用通信来要求它自己停止;

示例:并发时钟服务器

示例:并发回声服务器

通道

  1. 通道是goroutine之间的连接,可以让他们之间通信;
  2. 使用内置make函数创建一个通道:ch := make(chan int)
  3. 通道是引用传递的,零值是nil
  4. 同种类型的通道可以使用==比较,当是同一个通道的引用时返回true
  5. 通道有两个主要操作:发送和接收,都使用<-操作符;通道在左时,操作符指向通道,为发送;
  6. 接收时,丢弃结果也是合法的:<-ch
  7. 通道支持关闭操作,使用内置的close函数来关闭:close(c)
  8. 在已关闭的通道上发送导致宕机,接收能够依次获取所有已发送的值,直到通道为空,随后获取通道元素类型对应的零值;
  9. 可以创建有缓冲的通道,也是使用make函数,提供第二个参数:ch := make(chan int, 3),缓冲容量为3
  10. 无缓冲通道也称为同步通道,发送方进入等待(asleep),接收方接收值后才被唤醒;
  11. 缓冲通道会在缓冲区满后发送时进入等待,直到缓冲区有空闲容量才唤醒;
  12. 可以使用x, ok := <-ch来判断读取时是否通道已关闭;
  13. 推荐:可以使用range ch循环,循环将在接收完最后一个值后关闭;即使先关闭了通道,也可以获取到值;
  14. 单向通道:<-chan只能接收,chan<-只能发送;
  15. 试图关闭一个只能接收的通道导致编译错误;
  16. 任何赋值操作中,将双向通道转为单向通道都是允许的,但反过来不行;
  17. 使用len(ch)获取通道中元素数量,使用cap(ch)获取通道缓冲容量;
  18. goroutine泄露:向无缓冲通道发送时没有goroutine来接收数据;

并行循环

  1. 由一些完全独立的子问题组成的问题称为高度并行,这是最容易实现的并行问题;
  2. sync.WaitGroup类型:可被多个goroutine安全操作的计数器;通过Add()方法增加计数;

示例:并发的网络爬虫

  1. 一种令牌形式:使用struct{}类型的缓冲通道,指定容量后,能向通道中发送则表明获取到了令牌;

使用select多路复用

  1. tick := time.Tick(1 * time.Second)获取一个<-chan Time通道,通道上每隔1s发送一个Time值;Tick函数产生的通道在应用的整个生命周期一致存在;如果

  2. select语法:

    select{
        case <-ch1:
        
        case x := <-ch2:
        
        case ch3<-y:
        
        default: // 当default标签存在时,当前没有通信的话会立刻执行default
        
    }
    
  3. select类似于switch,但是每个分支指定一种通道收发情况;select调用时,如果没有default标签,会持续等到直到一次通信的到来

  4. 如果多个情况同时满足select会随机挑选一个执行,保证每个通道有相同的机会选中;

  5. ticker := time.NewTicker(1 * time.Second)可以调用ticker.Stop()来停止ticker,避免其一直存活;

并发目录遍历

  1. ioutil.ReadDir()读取一个目录下的所有条目[]os.FileInfo
  2. 标签化的break label可以跳出多重循环;

取消

  1. 取消goroutine的问题:使用chan struct{},当这个通道关闭时,能通知到所有能访问到该通道的goroutine

  2. 然后在goroutine中使用select轮询该通道,通道关闭时则将该goroutine关闭;

    cancel := make(chan struct{})
    func cancelled() bool{
        select{
        case <-cancel :
    		return true
        default:
            return false
        }
    }
    func someGoroutine(){
        for{
            if c:=cancelled(); c{
                return
            }
        }
    }
    
  3. 如果用一个通道的值作为取消信号只能取消一个goroutine

示例:聊天服务器

  1. 服务器设计说明:
    • 主函数中:监听tcp连接,并执行聊天信息广播的goroutine;当有连接到来时,启动一个连接处理的goroutine
    • 广播函数中:select三种通道的消息:上线通道、下线通道、消息通道;前两者传输的是仅发送的string通道类型,将这种类型定义为client标志,并维护当前所有的clients:上线添加一个client,下线删除对应client。来消息时将消息发送到clients中的所有元素中(每个元素都是一个client,也就是仅发送的chan<- string);
    • 连接处理函数中:开启一个写信息的goroutine,用于将消息写到每个连接上;先在上线通道发送自己的client,广播上线消息;然后读取连接发送的消息,读取失败时则表明连接断开,在下线通道发送自己的client,广播下线消息,关闭连接;
    • 注意
      • map和通道都是引用类型,节省空间;
      • clients是广播函数中的局部变量,没有并发维护clients的问题;
      • 存在n个客户端时,总共有2n+2goroutine:主函数、广播函数、连接处理(读取数据)函数、写数据函数;
      • 各部分职责十分清晰:
        • 主函数:监听连接
        • 广播函数:维护客户端列表,广播消息
        • 连接处理函数:发送连接开始/结束信号,读取客户数据
        • 写数据函数:向本连接写数据
  2. 自己设计聊天服务器的话,或许:
    • 主函数中等待连接,维护clients数组,连接成功时添加client,连接断开时删除client,其中clientnet.Conn类型;
    • 各自监听自己连接的数据,数据到来时,添加client标记,将其推到广播通道中;
    • 广播函数中依次读取消息并发送给各个client
  3. 自己设计的服务器有些问题:
    • 如果正在广播某条消息的时候,有客户端上线或下线了怎么办(for循环的数组在循环中变化了)?—或许会通过加锁来完成;
    • 划分不清晰:教程中的划分下,假设通讯方式由tcp换成了udp、串口、can总线都可以实现广播,关键就在于将通讯/广播抽象了,更易扩展;

如有错误 ❌ ,欢迎指正 ☝️~

如有收获 🍗,可以考虑点赞👍/评论💬/收藏⭐️/关注👀,大家共同进步~


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值