《Concurrency in Go》阅读笔记 -- 第三章:Go语言并发组件

本文是《Concurrency in Go》第三章的读书笔记,重点介绍了Go语言的并发组件,包括goroutine的概念、特点、创建与并发实现,详细探讨了sync包中的WaitGroup、Mutex、RWMutex、Cond、Once和Pool,以及channel的使用和select语句。文章还涉及了goroutine中闭包运行的情况,展示了如何避免并发问题和资源同步。
摘要由CSDN通过智能技术生成

《Concurrency in Go》


本章节从goroutine入手,讲解go语言的各种并发原语。在讲解完goroutine之后,对于传统的内存同步访问的并发原语:sync包中的Mutex,RWMutex,Cond,Once,WaitGroup,Pool等进行了分析。在此之后着重讲了go语言的另一大特色:channel。在最后,讲解了如何结合channel的语法:select语句。

插一句题外话:这本书的中文版本的翻译就是一坨屎。


Chapter 3:Go’s Concurrency Building Blocks Go 语言并发组件


1. goroutine

goroutine是Golang中最基本的组织单位之一,每个go语言的程序都至少有一个goroutine:main goroutine,它在进程开始时自动创建并且启动。

1.1 什么是goroutine?

简单的说:goroutine是一个并发的函数,可以和别的代码块同时运行(不一定是并行的)。

至于如何使用go关键字来简单的创建一个goroutine,就不多讲了,看到这个博客的人估计没那么傻。

golang中的goroutine是这个世界上独一无二的东西。它不是OS线程,也不是绿色线程(由语言运行时管理的线程)。有些中文的翻译为轻量线程,但是事实上goroutine is a coroutine,也就是说goroutine是一个协程。协程是一种非抢占式的特殊线程(进程和线程是抢占式的)。协程不能被中断,但是协程尤多个允许暂停和重新进入的点。

1.2 goroutine的独到之处(和普通协程的区别)

goroutine的独到之处在于它们与golang的运行环境的深度集成。(原文是:What makes goroutines unique to Go are their deep integration with Go’s runtime. 这里的所谓golang的运行环境其实是特指的golang的runtime | 在中文翻译中为:它们与Go语言运行时的深度集成,这根本就不通顺嘛!)

goroutine定义了自己的暂停的方法和再切入的点。Go语言的runtime会观察goroutine的运行时的行为,并且在阻塞的时候自动挂起它们,然后在不被阻塞的时候再恢复。 在golang的runtime和goroutine的逻辑之间有一种优雅的伙伴一样的关系。

1.3 goroutine怎么实现并发

协程(coroutine)和goroutine都是隐式并发结构,这说明并发并不是协程的属性:必须同时托管多个协程,并且给每个协程一个执行的机会。

Golang的主机托管机制是一个M:N调度器,主要机制就是将M个由程序管理的线程映射到N个OS线程。而M:N调度器可以单独写一个博客了,这里就不再细说。

Golang遵循一个称为fork-join的并发模型。

  • fork是指在程序运行中的任意一点,它可以将执行的子分支和父节点同时运行。
  • join这个词是指的是在将来的某个时候,这个并发的执行分支将会合并在一起。

在fork-join模型中,掌握join的点是至关重要的,因为join点是保证程序的正确性和消除竞争条件的关键。而控制join点的关键技术是WaitGroup。

1.4 goroutine中闭包运行的情况

我们在快速创建goroutine的时候往往会选择使用匿名函数来创建,这就牵扯到了闭包中变量的引用问题:闭包可以从创建它们的作用域中获取变量,那么当这个闭包运行的时候,调用这些变量的方式是副本还是引用呢?

举个例子:

var wg sync.WaitGroup
salutation := "hello"
wg.Add(1)
go func() {
   
    defer wg.Done()
    salution = "welcome"
}()
wg.Wait()
fmt.Println("Out:", salutation)

我的得到的输出是:

Out: welcome

事实证明,goroutine在它们所创建的相同地址空间内执行。
从另一个角度再进行一个实验:

var wg sync.WaitGroup
for _, salutation := range []string{
   "hello", "greetings", "good day"} {
   
    wg.Add(1)
    go func() {
   
        defer wg.Done()
        fmt.Println(salutation)
    }()
}
wg.Wait()

这个程序我们期望得到的结果是:

hello
greetings
good day

以上的所有可能的排列组合,因为我们都知道并发所带来的竞争条件产生的影响,但是输出却让我们大吃一惊:

good day
good day
good day

当大家看到输出的时候应该已经明白了究竟是怎么回事:在输出之前,salutation就已经完成了迭代。

但是值得注意的一点是,既然迭代已经结束,为什么还能使用salutation的引用呢?这个就和golang的GC有关,golang的GC会小心的把salutation的引用从内存转移到堆,以便能够继续使用。

所以正确的程序应该这样编写:

var wg sync.WaitGroup
for _, saluta
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值