阿坦
@37157522
Go 语言并发实现的简明解析
Golang Go语言 Go 语言入门 编程
2022-06-01 14:54
Words count: 2071
众所周知 Go 语言是高并发开发利器,它可以使多核(多线程)CPU 的性能得到充分利用。那么,Go 具体是怎么实现并发的呢?这篇教程试图用最简洁的文字和代码给你讲清楚。
***版权声明***
本文首发于 PRSDigg(顶呱呱) © 2022 阿坦
转载请注明出处
原创不易,请多转发点赞

众所周知 Go 语言是高并发开发利器,它可以使多核(多线程)CPU 的性能得到充分利用。

那么,Go 具体是怎么实现并发的呢?这篇教程试图用最简洁的文字和代码给你讲清楚。

goroutine

首先,需要引入一个概念:goroutine.

线程这个概念,可能知道的人会更多一些。我想借助这个概念来帮助你搞明白 goroutine.

当我们在进行一项任务,比如打开一个 word 文档,可以同时在它里面进行打字、拼写检查、打印等。同时干这几件事,就是在同时运行多项子任务。这每一个子任务——打字、拼字检查——它们就分别是一个线程。

在 Go 语言里面,也类似,在执行一段 Go 语言代码的时候,可以同时开启几项不同的工作,让它们交替进行(并发)或者同时进行(并行)。每一个并发(并行)的工作,就是一个 goroutine. 我听到有些老师把它叫做 Go 程,我觉得很贴切易懂。所以我想,不如接下来我们也把它叫做「Go 程」吧。

那么,具体在代码里是怎么实现的呢?

代码实现

在 Go 语言里,一段代码一般至少要有 3 个部件:包名、引入的包、以及 main() 函数。

我们先来看一个简单的 Go 代码:

package main

import "fmt"

func main() {
    fmt.Println("Hello")
    fmt.Println("World")
    fmt.Println("Say something.")
}

整个 main() 函数的任务是输出三个字符串 "Hello", "World", "Say something". 来看看运行的结果:

saySomething
saySomething

现在,假设我们想把输出 "Hello" 与 输出 "World" 这两个语句变成「Go 程」,我们只需要在它们前面加上关键字 go.

看下面这段代码,这样就算是为 main() 函数添加了两个「Go 程」:

package main

import "fmt"

func main() {
    go fmt.Println("Hello")
    go fmt.Println("World")
    fmt.Println("Say something.")
}

这意味着说,输出 "Hello" 这句语句,和输出 "World" 这句语句会同时启动,说不好哪一句会先输出在屏幕上。

来,运行一下看看:

add2goroutine
add2goroutine

诶,奇怪!"Hello", "World" 都不再输出了。发生了什么?

原来 main() 函数也是一个「Go 程」。现在输出 "Hello", "World" 的两个函数已经不再是「main Go 程」的一部分,而是独立出去的两个「Go 程」。

于是,fmt.Println("Say something")不会再等它们,「main Go 程」还没等里面的两个「Go 程」执行完毕,就已经提前结束了。

再于是,在「main go 程」里面的两个「Go 程」再也没有机会执行了。

「Go 程」不能有返回值

正是由于上述的这个特性,Go 语言规定「Go 程」不能有返回值。因为有可能在 main() 函数中已经要用这个返回值来,但是返回它的那个「Go 程」还没执行完毕,这就产生了问题。

那么,「Go 程」是不是就没办法传值了呢?

非也。Go 语言可以定义 channel. 下面就具体来说说这个 channel

channel

先来看看定义 channel 的语法:

var + chennel 名 + chan 关键字 + 这个 channel 将要保存的值的类型

// 定义 channel 
var myChannel chan int

像这样,就定义好了一个保存整型值的 channel, 它的名字是 myChannel.

和切片、map 一样,定义好之后还不能直接使用,要先 make:

myChannel = make(chan int) 

接下来,这个 myChannel 就可以用来传值了。

具体怎样传值给 channel 呢?用一个 < 加上一个小横线 -, 把值指向具体的 channel. 就像这样:

myChannel <- 3.14

那又怎么从 channel 中把值拿出来呢?用同样的符号 <-,把 channel 放到右边:

<-myChannel

实操一下

package main

import "fmt"

func HelloWorld(channel chan string) {
    channel <- "Hello"
    channel <- "World"
}

func main() {
    var myChannel chan string
    myChannel = make(chan string)
    go HelloWorld(myChannel)
    fmt.Println(<-myChannel)
    fmt.Println(<-myChannel)
    fmt.Println("Say something.")
}

输出结果:

myChannel
myChannel

把上面的代码复制到 Go Play 亲自去运行感受一下,学习效果会更佳。

如果对 Go 的语法有基础了解的话,建议按照你对代码工作逻辑的猜想,在此基础上设法删删改改,去验证看看你的猜想是否正确。

channel 的阻塞机制

channel 会通过阻塞来保障它的储值和取值不发生混乱。

同一个 channel,在接收一个值后就会阻塞。直到这个 channel 中的值被取出——即 <-channel 被执行——才会解除阻塞,继续接收下一个值;

取值后同样会阻塞。直到这个 channel 再次接受新的值,才会解除阻塞,之后才会可以对这个 channel 进行再一次取值。

package main

import (
    "fmt"
    "time"
)

func HelloWorld(channel chan string) {
    channel <- "Hello"
    for i := 0; i < 5; i++ {
        fmt.Println("channel sleeping")
        time.Sleep(time.Second)
    }
    fmt.Println("channel wakes up!")
    channel <- "World"
}

func main() {
    myChannel := make(chan string)
    go HelloWorld(myChannel)
    fmt.Println(<-myChannel)
    fmt.Println(<-myChannel)
    fmt.Println("Say something.")
}

上面的代码中 HelloWorld() 函数做了一点小改动,在 channel 接收了值 "Hello" 后,会暂停 5 秒,再接着执行接收 "World" 值命令。

addSleep
addSleep

从结果中可以看到,我们延缓了 channel 第二次接收值,在 main() 函数中并没有任何延缓执行的命令。但是 main() 函数中的第二次 channel 取值命令被阻塞了。等到 5 秒过去 channel 第二次接收值完成之后,main() 函数中的阻塞才得以解除,于是新接收到的值 "World" 才被第二句 fmt.Println(<-myChannel) 输出出来。

小结

Go 的并发实现简单朴素,两个简单的东西 goroutine, channel 搞定。像 JavaScript 等类似的语言,往往要花哨和复杂一些。

Arweave TX
fINl58J6ogBsAR7LonW2Wv9e-CtXmuqOcUx1zhDm9gc
Content Digest
75fc492fc96a8a6e3999254750827d328840b7d1da3e42859967b7c64b1d4cec
- 90.0 % More to go -
Pay 1.0 pUSD (≈$1.0) to continue reading
and receive early reader revenue (Rules)
This post is a virtural product selling on Quill, IT WILL NOT REFUND once paid. Please pay with caution.
Pay to Read
Already paid? Try to Login
0 : 0
1 times bought, 0 times reward
Comments
阿坦
@37157522
Subscribing: 45 Subscribers: 117
潮起潮落是什么都不为
Subscribe
Related Posts
Defi乐高之借贷协议(2)--MarginFi 隐身3个月通关编程实战项目,升级进取型思维分享。 macOS系统多开Google Chrome浏览器 从小白到科学家(二)之脱胎换骨 Invoke the closed source contract tutorial with MetaMask
Rules Stats Help Github Twitter Discord
阿坦
@37157522
Subscribing: 45 Subscribers: 117
潮起潮落是什么都不为
Subscribe
Related Posts
Defi乐高之借贷协议(2)--MarginFi 隐身3个月通关编程实战项目,升级进取型思维分享。 macOS系统多开Google Chrome浏览器 从小白到科学家(二)之脱胎换骨 Invoke the closed source contract tutorial with MetaMask
Rules Stats Help Github Twitter Discord