协程
- 用户态的轻量级线程;单个线程,用户态控制上下文切换;
- 开启一个协程:
go func_name()
注意事项
- 如果主线程退出了,则协程即使还没有执行完毕,也会退出;
- 协程在完成了自己的认为后,先于主线程结束;
- 为了防止主线程提前退出,使用:
time.Sleep(tmie.Second*5); sync.WaitGroup.Wait()
; sync.WaitGroup 可反复使用;
互斥锁同步协程
- 多个协程操作同一个数据;需要加入互斥锁;
Mutex
sync
包类型的值,不应被拷贝;高水平的同步使用管道更好一些;func (m *Mutex) Lock(); func (m *Mutex) Unlock()
;- 使用 Lock() 加锁后,不能再次加锁,直到调用 UnLock() 解锁后,才能再次加锁;适用于读写次数没有明显区别的场景;性能,效率相对较低;
读写锁
- 读写锁:
RWMutex
; - 适用于读次数远远多于写次数的场景;
- 读数据的时候,数据之间不会相互影响,写和读之间才会产生影响;
管道
- 管道本质就是一个数据结构-队列;
- 数据是先进先出;
- 自身保证线程安全,多个协程访问时,不需要加锁;
- 管道是有类型的,一个 string 的管道只能存放 string 类型的数据;
管道的定义
- var 变量名 chan 数据类型:
var ch chan string
; - chan 是管道的关键字;
- 操作函数,
buildin
包下:长度:len(intChan)
;容量:cap(intChan)
;关闭管道:close(intChan)
; - 数据类型是管道里的数据的类型;也即是管道的类型;
- 管道是引用类型,必须初始化才能写入数据,即 make 后才能使用;
intChan = make(chan int, 3)
,容量为3,不能存放大于容量的数据; - 管道的关闭: 管道关闭后,就不能再向管道写数据了,但是仍然可以从管道读取数据;
- 关于 close 函数:
func close(c chan<- Type)
内建函数close关闭信道,该通道必须为双向的或只发送的。它应当只由发送者执行,而不应由接收者执行,其效果是在最后发送的值被接收后停止该通道。
在最后的值从已关闭的信道中被接收后,任何对其的接收操作都会无阻塞的成功。
对于已关闭的信道,语句:x, ok := <-c
还会将ok置为false。
管道的遍历
- 使用
for-range
遍历; - 在遍历时,如果管道没有关闭,则会出现 deadlock 的错误;
- 在遍历时,如果管道 已经关闭,则会正常遍历数据,遍历完成后,就会退出遍历;
- 管道数据没有准备好时,会阻塞等待;
只读/写管道
- 管道可以声明未只读或者只写性质;
- 只写:
var intC chan <- int
; 只读:var intC <-chan int
; 注意箭头位置;
管道的 select 功能
- 解决多个管道的选择问题,也可以叫作多路复用,可以从多个管道中随机公平的选择一个来执行;
- case 后面必须进行的是 io 操作,不能是等值,随机去选择一个io操作;
- default: 防止 select 被阻塞主,加入 default;
defer + recover 机制处理错误
- 多个协程工作,其中一个协程出现panic,导致程序崩溃;
- 解决:利用 defer + recover 捕获 panic 进行处理,即使协程出现问题,主线程仍然不受影响可以继续执行;
func divide () {
defer func(){
err := recover()
if err != nil {
fmt.Println(" divide() 函数异常:", err)
}
}()
num1 := 10
num2 := 0
fmt.Println(num1, "/" ,num2 ,"=", num1/num2)
}
示例代码
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
var sum int
var lock sync.Mutex
func add() {
defer wg.Done()
for i := 0; i < 10; i++ {
sum = i + 1
}
}
func test() {
for i := 0; i < 5; i++ {
wg.Add(1)
go func(n int) {
fmt.Println(n)
wg.Done()
}(i)
}
wg.Wait()
}
func mutexAdd(){
defer wg.Done()
for i := 0; i < 10_0000; i++ {
lock.Lock()
sum += 1
lock.Unlock()
}
}
func mutexSub(){
defer wg.Done()
for i := 0; i < 10_0000; i++ {
lock.Lock()
sum -= 1
lock.Unlock()
}
}
var rwLock sync.RWMutex
func RWLockRead() {
defer wg.Done()
rwLock.RLock()
fmt.Println("读取数据...")
time.Sleep(time.Second)
fmt.Println("读取数据成功...")
rwLock.RUnlock()
}
func RWLockWrite() {
defer wg.Done()
rwLock.Lock()
fmt.Println("==>>修改数据...")
time.Sleep(time.Second * 10)
fmt.Println("==>>修改数据完成...")
rwLock.Unlock()
}
func chanTest() {
var intChan chan int
intChan = make(chan int, 3)
fmt.Printf("查看/证明管道的引用类型,intChan=%v\n", intChan)
fmt.Println("==>> 向管道写入数据:")
intChan <- 10
num := 20
intChan <- num
intChan <- 30
fmt.Println("intChan 管道的长度:", len(intChan))
fmt.Println("intChan 管道的容量:", cap(intChan))
fmt.Println("==>> 从管道读取数据:")
num1 := <- intChan
fmt.Println("读到的第一个数据:", num1)
fmt.Println("intChan 管道的长度:", len(intChan))
fmt.Println("intChan 管道的容量:", cap(intChan))
fmt.Println("==>> 未关闭管道,遍历:")
fmt.Println("==>> 关闭管道:")
close(intChan)
num2 := <- intChan
fmt.Println("管道关闭后,可以读取数据,第二个数据:", num2)
fmt.Println("==>> 关闭管道,遍历:")
forRangeChan(intChan)
}
func forRangeChan(c chan int) {
for val := range c {
fmt.Println("val=", val)
}
}
func writeData(intChan chan int) {
defer wg.Done()
for i := 0; i < 20; i++ {
intChan <- i
fmt.Println("向管道中写入数据:", i)
time.Sleep(time.Second)
}
close(intChan)
}
func readData(intChan chan int) {
defer wg.Done()
forRangeChan(intChan)
}
func goroutineXChan(){
wg.Add(2)
intChan := make(chan int, 50)
go writeData(intChan)
go readData(intChan)
wg.Wait()
}
func readOnlyWriteOnlyChan(){
var intChanWrite chan <- int
intChanWrite = make(chan int, 3)
intChanWrite <- 10
fmt.Println("intChanWrite=", intChanWrite)
var intChanRead <- chan int
if intChanRead != nil {
num := <- intChanRead
fmt.Println("num=", num)
}
}
func chanSelect() {
wg.Add(2)
ints := make(chan int, 1)
go func() {
time.Sleep(time.Second * 5)
ints <- 10
wg.Done()
}()
strChan := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
strChan <- "go-go"
wg.Done()
}()
select {
case v := <- ints:
fmt.Println("ints=", v)
case v := <- strChan:
fmt.Println("strChan=", v)
default:
fmt.Println("防止 select 被阻塞")
}
wg.Wait()
}
func printNum() {
for i := 0; i < 10; i++ {
fmt.Println("i=", i)
}
}
func divide () {
defer func(){
err := recover()
if err != nil {
fmt.Println(" divide() 函数异常:", err)
}
}()
num1 := 10
num2 := 0
fmt.Println(num1, "/" ,num2 ,"=", num1/num2)
}
func deferRecoverTest() {
go divide()
go printNum()
time.Sleep(time.Second * 5)
}
func main() {
fmt.Println("==>> test:")
test()
fmt.Println("==>> add:")
wg.Add(1)
go add()
wg.Wait()
fmt.Println("sum=", sum)
fmt.Println("==>> Mutex 使用示例:")
wg.Add(2)
go mutexAdd()
go mutexSub()
wg.Wait()
fmt.Println("sum=", sum)
fmt.Println("\n==>> RWMutex 使用示例:")
wg.Add(6)
for i := 0; i < 5; i++ {
go RWLockRead()
}
go RWLockWrite()
wg.Wait()
fmt.Println("sum=",sum)
fmt.Println("\n==>> 管道:")
chanTest()
fmt.Println("\n==>> 协程和管道:")
goroutineXChan()
fmt.Println("\n==>> 只读/写管道:")
readOnlyWriteOnlyChan()
fmt.Println("==>> select-chan:")
chanSelect()
fmt.Println("==>> defer+recover:")
deferRecoverTest()
}
运行结果
==>> test:
4
0
1
2
3
==>> add:
sum= 10
==>> Mutex 使用示例:
sum= 10
==>> RWMutex 使用示例:
==>>修改数据...
==>>修改数据完成...
读取数据...
读取数据...
读取数据...
读取数据...
读取数据...
读取数据成功...
读取数据成功...
读取数据成功...
读取数据成功...
读取数据成功...
sum= 10
==>> 管道:
查看/证明管道的引用类型,intChan=0xc00020e000
==>> 向管道写入数据:
intChan 管道的长度: 3
intChan 管道的容量: 3
==>> 从管道读取数据:
读到的第一个数据: 10
intChan 管道的长度: 2
intChan 管道的容量: 3
==>> 未关闭管道,遍历:
==>> 关闭管道:
管道关闭后,可以读取数据,第二个数据: 20
==>> 关闭管道,遍历:
val= 30
==>> 协程和管道:
向管道中写入数据: 0
val= 0
向管道中写入数据: 1
val= 1
向管道中写入数据: 2
val= 2
向管道中写入数据: 3
val= 3
向管道中写入数据: 4
val= 4
向管道中写入数据: 5
val= 5
向管道中写入数据: 6
val= 6
向管道中写入数据: 7
val= 7
向管道中写入数据: 8
val= 8
向管道中写入数据: 9
val= 9
向管道中写入数据: 10
val= 10
向管道中写入数据: 11
val= 11
向管道中写入数据: 12
val= 12
向管道中写入数据: 13
val= 13
向管道中写入数据: 14
val= 14
向管道中写入数据: 15
val= 15
向管道中写入数据: 16
val= 16
向管道中写入数据: 17
val= 17
向管道中写入数据: 18
val= 18
向管道中写入数据: 19
val= 19
==>> 只读/写管道:
intChanWrite= 0xc00020e080
==>> select-chan:
防止 select 被阻塞
==>> defer+recover:
i= 0
i= 1
i= 2
i= 3
i= 4
i= 5
i= 6
i= 7
i= 8
i= 9
divide() 函数异常: runtime error: integer divide by zero