这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
协程
协程与线程的对比
相比于c++里面的线程来说。GO语言的协程可以充分发挥CPU的多核性能,高效运行程序。
线程:有时被称为轻量级进程(Lightweight Process,LWP),是操作系统调度(CPU调度)执行的最小单位。其栈是MB级别的。
协程:是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。其栈是KB级别的。
协程相比于线程的优势
-
协程最大的优势在于“轻量级”:可以轻松创建上万个而不会导致系统资源衰竭。而线程和进程通常很难超过1万个。这也是协程可以称为“轻量级线程”的原因。
-
极高的执行效率:因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显;
-
不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
协程的创建
只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。
package concurrence
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)// 函数输入的是i
}
time.Sleep(time.Second)
}
channel (通道)
并发核心单元通过 channel 就可以发送或者接收数据进行通讯,可以降低编程的难度。
协程运行在相同的地址空间,因此访问共享内存必须做好同步。多程更加推荐通过通信共享内存而不是共享内存来实现通信,那么我们就会使用到通道。
channel 的使用
和其他数据结构相似(map、切片),channel也一个对应make创建的底层数据结构的引用。
当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil。
make(chan Type) //等价于make(chan Type, 0),无缓冲通道
make(chan Type, capacity)// 有缓冲通道
无缓冲的通道是指在接收前没有能力保存任何数据值的通道。
package concurrence
func CalSquare() {
// src 是无缓存 channel
src := make(chan int)
// dest 是缓存大小为3的 channel
dest := make(chan int, 3)
// A 子协程发送0~9,放到channel src中
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
close(src)// 关闭通道
}()
// B 子协程从 src 中提取A子协程发送的0~9,计算平方后放进 dest 中
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
close(dest)// 关闭通道
}()
// 主协程,从 dest 提取出 B 子协程的计算结果并输出
for i := range dest {
//复杂操作
println(i)
}
}
当没有值需要发送到channel的话,也让接收者也能及时知道没有多余的值可接收将是有用的,就可以让接收者停止不必要的接收等待。我们可以通过内置的close函数来关闭channel实现。
这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。
阻塞: 由于某种原因数据没有到达,当前go程(线程)持续处于等待状态,直到条件满足,才解除阻塞。
同步: 在两个或多个go程(线程)间,保持数据内容一致性的机制。
并发安全 Lock
同时只有一个gotoutine可以访问共享资源 有时候go代码中可能会存在多个goroutine同时操作一个资源,这种情况会发生竞争问题。这时我们就需要锁来保证同时只有一个gotoutine可以访问共享资源,以此来保证资源的安全。
package concurrence
import (
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
// 当对x执行操作时加锁防止其他协程对此进行操作
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()
}