zoukankan      html  css  js  c++  java
  • Go 语言并发之道

    Go 语言的并发哲学

     1、你想要转让数据的所有权吗?

      如果你有一块产生计算结果并想共享这个结果给其他代码块的代码,你所实际做的事情是传递了数据的所有权。可以创建一个带缓存的channel来实现一个低成本的在内存中的队列来解耦生产者和消费者。

    2、你是否试图在保护某个结构的内部状态?

      通过使用内存访问同步原语,可以为你的调用者隐藏关于重要代码块的实现细节。

    3、你是否试图协调多个逻辑片段?

      请记住,channel本质上比内存访问同步原语更具可组合性。

    4、这是一个对性能要求很高的临界区吗?

      如果程序的某个部分,事实证明是主要的性能瓶颈,比程序的其他部分要慢几个数量级,使用内存访问同步原语会有帮助。

    Go 语言的并发性哲学可以这样总结:追求简洁,尽量使用channel,并且认为goroutine 的使用是没有成本的。

    Go 语言并发组件

    goroutine

    main 也是一个 goroutine。简单地说,goroutine 是一个并发的函数,与其他代码一起运行。可以简单地在一个函数之前添加 go 关键字来触发。同样可以作为匿名函数来使用。

    goroutine 是如何工作的?OS线程?绿色线程(由语言运行时管理的线程)?我们能创造多少个goroutine?

    Go 语言中的 goroutine 是独一无二的。它们不是OS线程也不是绿色线程,它们是更高级别的抽象,称为协程,是一种非抢占式的简单并发子goroutine,也就是说它们不能被中断。取而代之的是,协程有多个点,允许暂停或重新进入。Go 语言的 runtime 会观察 goroutine 的运行时行为,并在它们阻塞时自动挂起它们,然后在它们不被阻塞时恢复它们。

    Go 语言的主机托管机制是一个名为 M:N 调度器的实现。将M个绿色线程映射到N个OS线程。然后将 goroutine 安排在绿色线程上。Go 语言遵循一个称为 fork-join 的并发模型。fork 这个词指的是在程序中的任意一点,它可以将执行的子分支与其父节点同时运行。join 这个词指的是在将来某个时候,这些并发的执行分支将会合并在一起。

    闭包可以从创建他们的作用域中获取变量的引用。

    WaitGroup

    你可以将 WaitGroup 视为一个并发安全的计数器:调用通过传入的整数执行add方法增加计数器的增量,并调用done放大对计数器进行递减。wait阻塞,直到计数器为零。

    cond

    https://zhuanlan.zhihu.com/p/367166977

    https://zhuanlan.zhihu.com/p/401242064

    大部分场景下使用 channel 是比 sync.Cond方便的。不过我们要注意到,sync.Cond 提供了 Broadcast 方法,可以通知所有的等待者。想利用 channel 实现这个方法还是不容易的。我想这应该是 sync.Cond 唯一有用武之地的地方。如果要实现广播唤起效果,只需要利用 context 的链式取消特性,也能达到该效果。

    为什么 cond 不能复制?

    once

    https://studygolang.com/articles/21852?fr=sidebar

    sync.once 是一种类型,即使在不同的 goroutine 上,也只会调用一次 Do 方法处理传递进来的函数。即使传的函数不一样,Do 也只会处理第一次传入的函数。

    https://www.jianshu.com/p/8fbbf6c012b2
    为什么 sync.pool 不能被拷贝?
    为了复用已经使用过的对象,来达到优化内存使用和回收的目的。说白了,一开始这个池子会初始化一些对象供你使用,如果不够了呢,自己会通过new产生一些,当你放回去了之后这些对象会被别人进行复用,当对象特别大并且使用非常频繁的时候可以大大的减少对象的创建和回收的时间。还有一个重要的特性是,放进 Pool 中的对象,会在说不准什么时候被回收掉。所以如果事先 Put 进去 100 个对象,下次 Get 的时候发现 Pool 是空也是有可能的。不过这个特性的一个好处就在于不用担心 Pool 会一直增长,因为 Go 已经帮你在 Pool 中做了回收机制。这个清理过程是在每次垃圾回收之前做的。垃圾回收是固定两分钟触发一次。而且每次清理会将 Pool 中的所有对象都清理掉!因此 sync.Pool 缓存的期限只是两次 gc 之间这段时间。

    如何在多个 goroutine 之间使用同一个 pool 做到高效呢?官方的做法就是尽量减少竞争,因为 sync.pool 为每个 P(对应 cpu,不了解的童鞋可以去看看 golang 的调度模型介绍)都分配了一个子池,如下图:

    当执行一个 pool 的 get 或者 put 操作的时候都会先把当前的 goroutine 固定到某个P的子池上面,然后再对该子池进行操作。每个子池里面有一个私有对象和共享列表对象,私有对象是只有对应的 P 能够访问,因为一个 P 同一时间只能执行一个 goroutine,因此对私有对象存取操作是不需要加锁的。共享列表是和其他 P 分享的,因此操作共享列表是需要加锁的。

     

    1、什么情况下适合使用sync.Pool呢?为什么 sync.Pool 不适合用于像 socket 长连接或数据库连接池?

    因为,我们不能对 sync.Pool 中保存的元素做任何假设,以下事情是都可以发生的:

    1. Pool 池里的元素随时可能释放掉,释放策略完全由 runtime 内部管理;

    2. Get 获取到的元素对象可能是刚创建的,也可能是之前创建好 cache 住的。使用者无法区分;

    3. Pool 池里面的元素个数你无法知道;

    所以,只有的你的场景满足以上的假定,才能正确的使用 Pool 。sync.Pool 本质用途是增加临时对象的重用率,减少 GC 负担。划重点:临时对象。所以说,像 socket 这种带状态的,长期有效的资源是不适合 Pool 的。Pool 不适用于有状态的对象,只适用于无状态的临时大对象的复用。

    channel

    for range channel 时,如果 channel 已经 close 这个循环会自动退出。由于一个被关闭的 channel 可以被无限次读取,所以关闭 channel 也是一种同时给多个 goroutine 发送信号的方法。

    缓冲 channel 是一个内存中的 FIFO 队列

    尽量保持 channel 所有权的范围很小(创建、写入和关闭),消费者函数只能执行 channel 的读取方法。

    Select

    一个 select 模块包含一系列的 case 语句,case 语句没有顺序,如果没有满足任何条件,执行也不会失败。如果多个 case 语句都可以执行将会随机挑选一个。如果没有任何语句可以执行会一直阻塞,可以使用 time.After 来设置超时时间,也可以通过 default 继续执行自己的操作

    GO 语言的并发模式

  • 相关阅读:
    PHP unset销毁变量并释放内存
    编码问题
    编程中关于对时区的理解(语言PHP)
    简单树结构的实现
    设计模式
    Selenium WebDriver +Python讲解
    PS入门基础-魔幻调色
    .NET Core、.NET Standard 、ASP.NET Core 和 .NET Framework 有什么不同?
    电商设计
    NPOI相关学习文档
  • 原文地址:https://www.cnblogs.com/DSKer/p/15408328.html
Copyright © 2011-2022 走看看