后端实践--go
并发编程 青训营
go
的擅长领域就是高并发编程!!由于go
语言的出现时间较晚,已经是并发编程较为普及的时代,所以go
语言在设计的时候就天生支持并发。
先了解一下并发和并行的概念。并发是指在一个时间段内多个任务共同推进,如核心上跑多个任务,一般会由于时间片轮转调度这多个任务在一个时间段内看上去同时在推进;而并行是指安排多个核心,每个核心去执行者多个任务中的一个或多个。
go
通过goroutine
实现并发。goroutine
类似于线程,属于==用户态的线程==,比内核态的线程更轻量。goroutine
是go
运行时调度完成的,而内核态线程是由OS
调度的。
一般goroutine
配合函数使用。相当于启动一个新的线程去执行这个函数。
channel
: 在多个goroutine
间通信。
1. goroutine
func hello(){ fmt.Println("aaaa") } // 这里会创建一个主goroutine // 主goroutine退出了,那么其他从属的goroutine也就退出了 func main(){ // 启动一个新的goroutine(线程)执行这个函数 go hello() // 主执行流正常往下执行 fmt.Println("bbb") tmie.Sleep(time.Second) }
等待goroutine
结束 (有点像信号量)
// 声明全局等待组变量 var wg sync.WaitGroup func hello() { defer wg.Done() // 告知当前goroutine完成 -- 操作 fmt.Println("hello") } func main() { wg.Add(1) // 登记1个goroutine ++操作 go hello() fmt.Println("你好") wg.Wait() // 阻塞等待登记的goroutine完成 等到wg减到0 }
goroutine
调度 --- GMP
调度系统
G
: goroutine
每执行一次go f()
就创建一个 G,包含要执行的函数和上下文信息。
M
: Machine
实际干活的。和内核线程是一一映射的关系。
P
管理者,管理goroutine
。管理 goroutine
执行所需的资源,最多有 GOMAXPROCS
个。
GOMAXPROCS
// 设置执行当前任务的CPU核心数,默认是所有的核心 runtime.GOMAXPROCS(4) runtime.NumCPU()
goroutine
和OS
线程的区别
- 一个
os
线程可以对应多个goroutine
go
程序也可以同时使用多个os
线程- 他们是多对多的关系
2. channel
就可以理解为一个类型。--- 引用类型!
三个常用的引用类型:切片、map
、chan
==需要make
初始化。==
实现多 goroutine
间通信。
有点像管道通信,遵循先入先出原则。
声明通道但没有初始化时其为nil
,这时读取和写入都会都会阻塞。
自带访问控制机制:==通道开启时==,当通道为空时取值会阻塞住;当通道为满时写入也会阻塞住。通道关闭且有缓冲区时:不能写入,但是可以读取值直到读完。
// 声明一个channel管道,只能存放int类型的变量 var a chan int var b chan []string ... // 初始化(不带缓冲区) a = make(chan int) // 初始化(带缓冲区),代缓冲区的通道可以把接收的的数据先存到通道里 b = make(chan []string, 16)
chan
操作 <-
向通道里写:a <- 1
从通道里读:ch := <- a
关闭通道:close(a)
, 这个别忘了!
// 循环取值 a := make(chan int, 10) // 一样的: // 若通道是开启的没有close,那么这里取完值后会阻塞!!! // 只有通道关闭时取完值后会返回退出循环 for i := range a{ // ... } // 手动判断停止 for{ // 若通道是开启的没有close,那么这里取完值后会阻塞!!! // 只有通道关闭时取完值后会返回:对应类型的零值和 false x,ok := <- a if !ok{ // 读完了 break } // ... }
确保只执行一次。
var once sync.Once // ... // 确保只关闭一次,关闭已经关闭了的chan会引发panic!! once.Do(func(){ close(a) })
单向通道
通常用于函数参数,限制为只允许读取或只允许写入的通道。
// 只能写入数据的通道 var a chan<- int // 只能读取数据的通道 var b <-chan int
通道仅用来作为标记等时可以这样用,节省空间。
// 通道类型是空结构体,更节省空间! var notice = make(chan struct{}, 10) // ... // 向notice通道里写入。 struct{} 是类型,再加一个 {} 表示实例化 notice <- struct{}{}
3. worker pool
(goroutine
池)
就是开启多个 goroutine
竞争执行任务。
4. select
多路复用
同一时刻有多个通道,选择其中某一个执行。
让其他未满足条件的通道同时等待,提高效率。
a := make(chan int, 1) select{ // 哪个满足条件就执行哪一个,有多个条件满足时随机执行一个! case x := <- a: // ... case a <- 10: // ... }
以上就是go
并发编程的简单知识,另外涉及到并发,就还有互斥锁和读写锁等各类锁的使用以及线程互斥和线程同步的知识,这里就不赘述了。