新旧模式对比:共享内存和通道

假设我们需要处理多任务,一个worker处理一项任务。

旧模式:使用共享内存进行同步

由各个任务组成的任务池共享内存,为了同步各个worker以及避免资源竞争,我们需要对任务池进行加锁保护

Sync.Mutex被用来保护临界区资源,同一时间只有一个go协程可以进入该临界区,如果同时出现多个go协程进入该临界区,会产生竞争,Pool结构就不能正常被更新,在传统模式中,worker可以被这样写,

func Worker(pool *Pool) {
    for {
        pool.Mu.Lock()
        // begin critical section:
        task := pool.Tasks[0]        // take the first task
        pool.Tasks = pool.Tasks[1:]  // update the pool of tasks
        // end critical section
        pool.Mu.Unlock()
        process(task)
    }
}

加锁可以保证一个任务只被一个worker处理;不加锁,会导致协程在第一个任务处发生切换,导致剩余的任务结果发生异常,一些worker获取不到任务,而一些任务会被分配给多个worker,加锁实现同步的方式,可以在工作协程比较少的时候工作的很好,但当协程数量很大,任务数也很多的时候,处理效率会因为频繁的加锁/解锁开销而降低,当工作协程数增加到一个阀值的时候,程序效率会急剧下降,这就成为了瓶颈。

新模式:使用通道

从通道得到新任务的过程没有任何竞争
随着任务数量增加,worker数量也应该相应增加,同时性能并不会像第一种方式那样下降明显

对于任何可以建模为Master-Worker范例的问题,一个类似于worker使用通道进行通信和交互、Master进行整体协调的方案都能完美解决。如果系统部署在多台机器上,各个机器上执行Worker协程,Master和Worker之间使用netchan或者RPC进行通信(参见15章)

怎么选择是该使用锁还是通道?
通道是一个较新的概念,本节我们着重强调了在go协程里通道的使用,但这并不意味着经典的锁方法就不能使用。go语言让你可以根据实际问题进行选择:创建一个优雅、简单、可读性强、在大多数场景性能表现都能很好的方案。如果你的问题适合使用锁,也不要忌讳使用它。go语言注重实用,什么方式最能解决你的问题就用什么方式,而不是强迫你使用一种编码风格。下面列出一个普遍的经验法则:

使用锁的情景:

访问共享数据结构中的缓存信息
保存应用程序上下文和状态信息数据
使用通道的情景:

与异步操作的结果进行交互
分发任务
传递数据所有权
当你发现你的锁使用规则变得很复杂时,可以反省使用通道会不会使问题变得简单些。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值