这是我参与「第三届青训营 -后端场」笔记创作活动的第3篇笔记,非常感谢字节跳动无偿地分享技术知识并细致地讲解指导。本篇笔记主要介绍go语言的多线程知识。
一、并发与并行
并发: 指一段时间内,系统能处理多个任务,不要求是同时执行;
并行: 指系统能同时处理多个任务,即同一个时刻就能处理多个任务。
显然,并行是并发的子集,需要有多个核的CPU,多个任务同时在不同的核上运行,是并发的一种实现方式。
并发的另一种实现方式是共享时间片,即把一段时间(如1秒)分成多个时间片(如10个100ms),然后就可以让多个任务分别在不同的时间片上运行,最终得到一段时间内处理多个任务的效果。
二、Go语言协程
Go routine是go语言本身自带的功能,能够在语言层面支持并发,以更高的效率来开发多线程程序。
Go routine被称为协程,能在用户态下完成全过程,不需要陷入操作系统的内核态。线程是一种轻量级进程,而协程就是轻量级线程。线程的栈是MB级别,协程的栈是KB级别,协程的开销更小;一个进程可以跑多个线程,一个线程可以跑多个协程,协程的并行粒度更小。因此,Go应用程序可以轻松地并发运行数千个Goroutines,并行性能大大提高。
开启协程的语法非常简单,只需要 “ go 函数名(参数列表) ” 即可,也就是在函数调用前面加上go关键字。下面的代码演示了同时开启五个协程,go后面是定义了一个无名函数并调用,调用时传入了参数i。
import (
"fmt"
"time"
)
func hello(i int) {
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
三、通道Channel
Go语言采用CSP(communicating sequential processes)并发模型,即“不要以共享内存的方式来通信,相反,要通过通信来共享内存”。
我们可以简单理解为,不需要多个进程同时运行去主动访问共享内存,而是通过通道(channel)以通信顺序来依次执行进程。
go语言中通道对应的关键字为chan,用法如下:
func CalSquare() {
src := make(chan int) //无缓冲通道
dest := make(chan int, 3) //缓冲区大小为3个int值的通道
go func() { //产生[0,9]的int值传入通道src
defer close(src) // 延迟执行语句,return之前会倒序执行所有的defer
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() { //从src通道中依次取出数据,并将其平方传入通道dest
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest { //从dest通道中依次取出数据并打印
//复杂操作
println(i)
}
}
四、同步
Go语言的sync库中提供了两种同步方式,互斥锁Mutex 和 等待组WaitGroup。
互斥锁是传统的同步方式,有上锁和解锁操作,可以看作是值只能为0和1的信号量。
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)
}
等待组表示一个需要等待的go runtine集合,有Add、Done、Wait操作,可以分别看作是信号量的V操作、P操作、值为0操作。
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5) // 信号量+5
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done() // 信号量-1
hello(j)
}(i)
}
wg.Wait() //阻塞知道信号量为0
}