Go最新Go 专栏|并发编程:goroutine,channel 和 sync,2024年最新成功跳槽阿里

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

for {

for _, r := range -\|/ {

fmt.Printf(“\r%c”, r)

time.Sleep(delay)

}

}

}

func fib(x int) int {

if x < 2 {

return x

}

return fib(x-1) + fib(x-2)

}

从执行结果来看,成功计算出了斐波那契数列的值,说明程序在 spinner 处并没有阻塞,而且 spinner 函数还一直在屏幕上打印提示字符,说明程序正在执行。

当计算完斐波那契数列的值,main 函数打印结果并退出,spinner 也跟着退出。

再来看一个例子,循环执行 10 次,打印两个数的和:

package main

import “fmt”

func Add(x, y int) {

z := x + y

fmt.Println(z)

}

func main() {

for i := 0; i < 10; i++ {

go Add(i, i)

}

}

有问题了,屏幕上什么都没有,为什么呢?

这就要看 Go 程序的执行机制了。当一个程序启动时,只有一个 goroutine 来调用 main 函数,称为主 goroutine。新的 goroutine 通过 go 关键词创建,然后并发执行。当 main 函数返回时,不会等待其他 goroutine 执行完,而是直接暴力结束所有 goroutine。

那有没有办法解决呢?当然是有的,请往下看。

channel

一般写多进程程序时,都会遇到一个问题:进程间通信。常见的通信方式有信号,共享内存等。goroutine 之间的通信机制是通道 channel。

使用 make 创建通道:

ch := make(chan int) // ch 的类型是 chan int

通道支持三个主要操作:sendreceiveclose

ch <- x // 发送

x = <-ch // 接收

<-ch // 接收,丢弃结果

close(ch) // 关闭

无缓冲 channel

make 函数接受两个参数,第二个参数是可选参数,表示通道容量。不传或者传 0 表示创建了一个无缓冲通道。

无缓冲通道上的发送操作将会阻塞,直到另一个 goroutine 在对应的通道上执行接收操作。相反,如果接收先执行,那么接收 goroutine 将会阻塞,直到另一个 goroutine 在对应通道上执行发送。

所以,无缓冲通道是一种同步通道。

下面我们使用无缓冲通道把上面例子中出现的问题解决一下。

package main

import “fmt”

func Add(x, y int, ch chan int) {

z := x + y

ch <- z

}

func main() {

ch := make(chan int)

for i := 0; i < 10; i++ {

go Add(i, i, ch)

}

for i := 0; i < 10; i++ {

fmt.Println(<-ch)

}

}

可以正常输出结果。

主 goroutine 会阻塞,直到读取到通道中的值,程序继续执行,最后退出。

缓冲 channel

创建一个容量是 5 的缓冲通道:

ch := make(chan int, 5)

缓冲通道的发送操作在通道尾部插入一个元素,接收操作从通道的头部移除一个元素。如果通道满了,发送会阻塞,直到另一个 goroutine 执行接收。相反,如果通道是空的,接收会阻塞,直到另一个 goroutine 执行发送。

有没有感觉,其实缓冲通道和队列一样,把操作都解耦了。

单向 channel

类型 chan<- int 是一个只能发送的通道,类型 <-chan int 是一个只能接收的通道。

任何双向通道都可以用作单向通道,但反过来不行。

还有一点需要注意,close 只能用在发送通道上,如果用在接收通道会报错。

看一个单向通道的例子:

package main

import “fmt”

func counter(out chan<- int) {

for x := 0; x < 10; x++ {

out <- x

}

close(out)

}

func squarer(out chan<- int, in <-chan int) {

for v := range in {

out <- v * v

}

close(out)

}

func printer(in <-chan int) {

for v := range in {

fmt.Println(v)

}

}

func main() {

n := make(chan int)

s := make(chan int)

go counter(n)

go squarer(s, n)

printer(s)

}

sync

sync 包提供了两种锁类型:sync.Mutexsync.RWMutex,前者是互斥锁,后者是读写锁。

当一个 goroutine 获取了 Mutex 后,其他 goroutine 不管读写,只能等待,直到锁被释放。

package main

import (

“fmt”

“sync”

“time”

)

func main() {

var mutex sync.Mutex

wg := sync.WaitGroup{}

// 主 goroutine 先获取锁

fmt.Println(“Locking (G0)”)

mutex.Lock()

fmt.Println(“locked (G0)”)

wg.Add(3)

for i := 1; i < 4; i++ {

go func(i int) {

// 由于主 goroutine 先获取锁,程序开始 5 秒会阻塞在这里

fmt.Printf(“Locking (G%d)\n”, i)

mutex.Lock()

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

goroutine 先获取锁,程序开始 5 秒会阻塞在这里

fmt.Printf(“Locking (G%d)\n”, i)

mutex.Lock()

[外链图片转存中…(img-FPTlYWL9-1715882622737)]
[外链图片转存中…(img-lbxQkSBI-1715882622737)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值