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.Mutex
和 sync.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()
fmt.Printf(“locked (G%d)\n”, i)
time.Sleep(time.Second * 2)
mutex.Unlock()
fmt.Printf(“unlocked (G%d)\n”, i)
wg.Done()
}(i)
}
// 主 goroutine 5 秒后释放锁
time.Sleep(time.Second * 5)
fmt.Println(“ready unlock (G0)”)
mutex.Unlock()
fmt.Println(“unlocked (G0)”)
wg.Wait()
}
RWMutex
属于经典的单写多读模型,当读锁被占用时,会阻止写,但不阻止读。而写锁会阻止写和读。
package main
import (
“fmt”
“sync”
“time”
)
func main() {
var rwMutex sync.RWMutex
wg := sync.WaitGroup{}
Data := 0
wg.Add(20)
for i := 0; i < 10; i++ {
go func(t int) {
// 第一次运行后,写解锁。
// 循环到第二次时,读锁定后,goroutine 没有阻塞,同时读成功。
fmt.Println(“Locking”)
rwMutex.RLock()
defer rwMutex.RUnlock()
fmt.Printf(“Read data: %v\n”, Data)
wg.Done()
time.Sleep(2 * time.Second)
}(i)
go func(t int) {
// 写锁定下是需要解锁后才能写的
rwMutex.Lock()
defer rwMutex.Unlock()
Data += t
fmt.Printf(“Write Data: %v %d \n”, Data, t)
wg.Done()
time.Sleep(2 * time.Second)
}(i)
}
wg.Wait()
}
总结
并发编程算是 Go 的特色,也是核心功能之一了,涉及的知识点其实是非常多的,本文也只是起到一个抛砖引玉的作用而已。
本文开始介绍了 goroutine 的简单用法,然后引出了通道的概念。
通道有三种:
-
无缓冲通道
-
缓冲通道
-
单向通道
最后介绍了 Go 中的锁机制,分别是 sync 包提供的 sync.Mutex
(互斥锁) 和 sync.RWMutex
(读写锁)。
goroutine 博大精深,后面的坑还是要慢慢踩的。
文章中的脑图和源码都上传到了 GitHub,有需要的同学可自行下载。
地址: https://github.com/yongxinz/gopher/tree/main/sc
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
- 单向通道
最后介绍了 Go 中的锁机制,分别是 sync 包提供的 sync.Mutex
(互斥锁) 和 sync.RWMutex
(读写锁)。
goroutine 博大精深,后面的坑还是要慢慢踩的。
文章中的脑图和源码都上传到了 GitHub,有需要的同学可自行下载。
地址: https://github.com/yongxinz/gopher/tree/main/sc
[外链图片转存中…(img-p2Iblpgq-1726015441178)]
[外链图片转存中…(img-cBAWfYi3-1726015441179)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。