这是我参与「第五届青训营」伴学笔记创作活动的第 2 天
前言
新年快乐!在新的一年的开始,我的笔记也开始了第二章,本次Go语言进阶主要分为并发编程、依赖管理、单元测试、项目实这四个板块。这次笔记主要围绕并发编程进行记录。
并发编程
并发VS并行
并发
并发是指多线程程序在一个核的CPU上运行
并行
并行时指多线程程序在多个核的CPU上运行
Goroutine
线程
线程:用户态,轻量级线程,栈 MB 级别。
协程
协程:内核态,线程跑多个协程,栈KB 级别。
协程的创建由Go语言来进行创建,更加轻量,线程可以并发的跑多个携程。所以Go语言更加适合并发场景。
快速打印hello goroutine,. O"hello goroutine : 4 例子:
package concurrence
import (
"fmt"
"time"
)
func hello(i int) {
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
# go 关键字就是添加携程
go func(j int) {
hello(j)
}(i)
}
# 进行一个堵塞操作
time.Sleep(time.Second)
}
CSP(communicating sequential processes)
CSP提供了一套「过程」的原语:
• 互斥锁:用来保证共享信道上数据不会同时被多个进程读取或写入。
• 信道:不同进程之间通信的主要手段。
• 选择:当一个进程需要从多个信道中选择数据时使用的原语。
• 超时:如果一个进程在对应申请上长时间得不到回应,则允许其放弃已发
Channel通道
Channel是一种用于组件间通讯的通道,它是由来自不同组件的消息流耦合在一起的机制。通过Channel,不同组件之间可以实现平台不可知的交互。
make 函数可以创建缓冲通道,参数为 缓冲区大小 。
ch := make(chan type, capacity)
- 无缓冲通道 make(chan int)
- 有缓冲通道 make(chan int, 2)
Channel通道数字平方任务例子:
package concurrence
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
# 子协程发送0-9数字
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
# 子协程计算输入数字的平方主协程输出最后的平方数
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
println(i)
}
}
并发安全Lock
Go语言中使用互斥锁来实现并发安全,常用的锁有sync.Mutex、sync.RWMutex和sync.Once,分别用于处理读写锁、单次执行和互斥锁。
1、sync.Mutex:是一种互斥量类型(Mutual Exclusion),当一个协程尝试获得Mutex时,其他想要获得此Mutex的协程将处于阻塞状态。
2、sync.RWMutex:是一种读写的互斥量类型(Read-Write Mutual Exclusion),当一个协程想要进行写操作时尝试获得Mutex时, 其他想要获得此Mutex的协程将处于阻塞状态; 如果一个或者多个协程仅仅想进行只读操作时, 那么将不会阻塞 对变量执行2000次加1操作,5个协程并发执行:
import (
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func Add() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock:", x)
}
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
WaitGroup
WaitGroup可以让我们使用简单高效的方式等待一堆goroutine完成。WaitGroup本质上是一个计数器,用来统计goroutine数量,它可以帮助开发者控制并发结构,在所有goroutine完成前main函数不会返回。
import (
"fmt"
"sync"
)
func HelloPrint(i int) {
fmt.Println("Hello WaitGroup :", i)
}
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
HelloPrint(j)
}(i)
}
wg.Wait()
}
func main() {
ManyGoWait()
}
小结
1、goroutine是Go并发的基础,通过go关键字和channel可以实现多个goroutine之间的通信。
2、sync包提供了Mutex互斥锁、WaitGroup同步处理和Once用于单次初始化的工具,以帮助开发者更好的进行并发编程。
3、Go提供了通用协议RPC来帮助大家实现服务器端和客户端之间的通信传输数据。
4、Context包提供了一个用于在web应用中传递上下文信息的方法。
5、Go语言内部封装了一套强大而高效的Select机制,可以帮助用户很好地响应各种I/O任务。