Go 语言进阶-并发编程| 青训营笔记

99 阅读4分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 2 天

前言

新年快乐!在新的一年的开始,我的笔记也开始了第二章,本次Go语言进阶主要分为并发编程、依赖管理、单元测试、项目实这四个板块。这次笔记主要围绕并发编程进行记录。

并发编程

并发VS并行

并发

并发是指多线程程序在一个核的CPU上运行 image.png

并行

并行时指多线程程序在多个核的CPU上运行 image.png

Goroutine

线程

线程:用户态,轻量级线程,栈 MB 级别。

协程

协程:内核态,线程跑多个协程,栈KB 级别。

image.png

协程的创建由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提供了一套「过程」的原语:

• 互斥锁:用来保证共享信道上数据不会同时被多个进程读取或写入。
• 信道:不同进程之间通信的主要手段。
• 选择:当一个进程需要从多个信道中选择数据时使用的原语。
• 超时:如果一个进程在对应申请上长时间得不到回应,则允许其放弃已发 image.png

Channel通道

Channel是一种用于组件间通讯的通道,它是由来自不同组件的消息流耦合在一起的机制。通过Channel,不同组件之间可以实现平台不可知的交互。

make 函数可以创建缓冲通道,参数为 缓冲区大小

ch := make(chan type, capacity)  
  • 无缓冲通道 make(chan int)
  • 有缓冲通道 make(chan int, 2)

image.png 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任务。