总结
go的并发理念是什么?
通信中传递信息
goroutine是什么?
goroutine是轻量级线程,由GPM调度器管理,利用多核处理器进行并发操作。channel是什么?
channel是多个goroutine之间通信的容器。select是什么?
select中每个case都是对一个通道的读写,它可以从任意一个可执行case中随机挑选造成死锁的原因?
一个Goroutine从通道中接收或向通道发送数据,但没有其他 Goroutine 执行相应的操作,就会导致死锁。sync包
WaitGroup 等待同步组、Mutex 互斥锁、RWMMutex 读写互斥锁、并发安全map、Once 仅调用一次、Pool 缓存池、Cond 条件变量
goroutine
是什么?
是轻量级线程,高效地利用多核处理器进行并发操作。
为什么?优点?体积小,优质的GMP调度管理
goroutine是否可以无限生成?
goroutine是否可以无限生成?毕竟有强大的GC和优质的调度器支撑
goroutine无限生成会导致:cpu大幅度上涨,内存占用不断上涨,最后会panic主进程崩溃
/*
goroutine无限生成
*/
func main() {
//模拟用户需求业务的数量
task_cnt := math.MaxInt64
for i := 0; i < task_cnt; i++ {
go func(i int) {
//... do some busi...
fmt.Println("go func ", i, " goroutine count = ", runtime.NumGoroutine())
}(i)
}
}
/*
结果:
panic: too many concurrent operations on a single file or socket (max 1048575)
Panic:在单个文件或套接字上有太多并发操作(最大1048575)
结论:
goroutine无限生成会导致
cpu大幅度上涨,内存占用不断上涨,最后会panic主进程崩溃
*/
goroutine-for-闭包的现象
/*
goroutine-for-闭包的现象
注意:for循环启动g,等创建g的时候不知道for循环到哪里了
*/
func main() {
slice := []int{0, 1, 2, 3, 4, 5}
for i, v := range slice {
go func() {
fmt.Println(i, v)
}()
}
time.Sleep(2 * time.Second)
}
goroutine与for循环的现象解决方案
/*
goroutine与for循环的现象
解决方案:将循环变量传递到goroutine
*/
func main() {
slice := []int{1, 2, 3}
for _, v := range slice {
go func(v int) {
fmt.Println(v)
}(v)
}
time.Sleep(1 * time.Second)
}
goroutine泄露场景?
如果对未初始化的channel.启动goroutine发送数据和接受数据都会阻塞.
使用chancel发送不接受,接受不发送
访问http请求,没有关闭请求.
成对操作忘记加锁,没有解锁
等待同步组使用不正确,add和done的数量不匹配
如何配查goroutine的数量?
单个函数:使用runtime的方法查看Goroutine运行数量。
生产环境:使用pprof实时监控Goroutine的数量。
channel
通道
通道是不同goroutine之间的通信容器
通道的声明和读写
ch<-1
result:=<-ch
关闭通道
注意:关闭的通道还可以获取数据但是零值
注意:往关闭的通道写数据会panic
注意:关闭已经关闭的通道会panic
从通道中循环读取数据
for-range循环读取通道的数据,会自动判断是否关闭
使用v,ok=<-ch获取通道数据ok判断通道是否关闭
管道缓冲
默认创建的都是非缓冲的channel,读写都是即时阻塞,缓冲channel自带一块缓冲区
可以暂时储存数据,如果缓冲区满了就会堵塞管道阻塞
注意:主函数往管道发送数据,goroutine接收数据现象.goroutine 没有足够的时间打印结果,由于主函数的退出,程序终止了解决方案:阻塞主程序
当向管道
死锁
管道阻塞,会导致程序停止
通道-初始化与读写
/*
通道-初始化与读写
语法:
通道初始化 ch:=make(chan int)
发送数据 ch<-1
接收数据 v:=<ch
*/
func main() {
ch := make(chan int) //通道初始化
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
ch <- 1 //发送数据
}()
go func() {
defer wg.Done()
v := <-ch //接收数据
fmt.Println(v)
}()
wg.Wait()
}
主g与g发送接收数据的现象
主g先发送或者接收数据死锁
g先发送或接收数据发生死锁,不会影响其他g
g先接收数据,主g发送数据
g先发送数据,主g接收数据
/*
主g先发送数据,g接收数据现象
注意:导致死锁,主goroutine发送数据会阻塞,之后就不执行了.
*/
func main() {
ch := make(chan int)
ch <- 1
fmt.Println("到这里已经死锁,执行不到")
go func() {
v := <-ch
fmt.Println(v)
}()
}
/*
主g先接收数据,g发送数据的现象
注意:导致死锁,主goroutine接收数据会阻塞,之后就不执行了.
*/
func main() {
ch := make(chan int)
v := <-ch
fmt.Println("到这里已经死锁,执行不到")
fmt.Println(v)
go func() {
ch <- 1
}()
}
/*
先启动goroutine发送数据,主g不接受数据的现象
注意:如果是goroutine阻塞,不会显示死锁。因为主goroutine还在运行没被阻塞。
*/
func main() {
ch := make(chan int)
go func() {
ch <- 1
}()
}
/*
先启动goroutine接收数据,主g不发送数据的现象
注意:如果是goroutine阻塞,不会显示死锁。因为主goroutine还在运行没被阻塞。
*/
func main() {
ch := make(chan int)
go func() {
v := <-ch
fmt.Println(v)
}()
}
/*
先启动goroutine发送数据的现象,主goroutine接收的数据
*/
func main() {
ch := make(chan int)
go func() {
ch <- 1
}()
v := <-ch
fmt.Println(v)
}
/*
启动一个goroutine接收数据的现象,主goroutine发送的现象:
注意:主goroutine发送数据后,关闭程序可能导致goroutine没有执行完.需要阻塞主goroutine
*/
func main() {
ch := make(chan int)
go func() {
v := <-ch
fmt.Println(v)
}()
ch <- 1
}
/*
启动一个goroutine接收数据的现象,主goroutine发送的现象:
解决方案:延迟主g
*/
func main() {
ch := make(chan int)
go func() {
v := <-ch
fmt.Println(v)
}()
ch <- 1
time.Sleep(time.Second)
}
管道-循环读出数据
/*
从通道中循环读取数据:
1.for-range自动判断通道是否关闭
2.使用v,ok=<-ch获取通道数据ok判断通道是否关闭
*/
func main() {
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
}
/*
从通道中循环读取数据:
1.for-range自动判断通道是否关闭
2.使用v,ok=<-ch获取通道数据ok判断通道是否关闭
*/
func main() {
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
close(ch)
}()
for {
v, ok := <-ch
if !ok {
break
}
fmt.Println(v)
}
}
管道-管道关闭的现象
/*
关闭管道的现象
注意:关闭的通道还可以获取数据但是零值
注意:往关闭的通道写数据会panic
注意:关闭已经关闭的通道会panic
*/
func main() {
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
close(ch)
}()
for {
v := <-ch
fmt.Println(v)
}
}
/*
关闭管道的现象
注意:关闭的通道还可以获取数据但是零值
注意:往关闭的通道写数据会panic
注意:关闭已经关闭的通道会panic
*/
func main() {
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
close(ch)
ch <- 3
}()
for v := range ch {
fmt.Println(v)
}
}
/*
关闭管道的现象
注意:关闭的通道还可以获取数据但是零值
注意:往关闭的通道写数据会panic
注意:关闭已经关闭的通道会panic
*/
func main() {
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
close(ch)
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
}
管道-缓冲管道
特点:通道满载时,发送操作被阻塞
特点:通道空时,接收操作被阻塞。
缓冲通道是具有缓冲区的通道,它允许在通道未被读取的情况下先向通道发送数据。
通常,非缓冲通道的发送和接收操作是同步的,也就是说发送操作会等待接收者准备好接收数据,而接收操作也会等待发送者准备好发送数据。
而缓冲通道则可以存储一定数量的值,这使得发送和接收操作可以不立即配对,只有当通道满载时,发送操作才会被阻塞,而接收操作则在通道为空时阻塞。
缓冲通道
/*
有缓冲channel
语法:c := make(chan 数据类型, 可以缓冲的长度)
缓冲通道可以储存一定量的数据
特点:通道满载时,发送操作被阻塞
特点:通道空时,接收操作被阻塞。
非缓存通道时收发即时阻塞的
*/
func main() {
ch := make(chan string, 2) // 创建一个能够缓存2个整数的通道
go func() {
for i := 0; i < 5; i++ {
ch <- fmt.Sprintf("读取数据%v", i)
fmt.Printf("写入数据%v\n", i)
}
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
}
/*
写入数据0
写入数据1
写入数据2
读取数据0
读取数据1
读取数据2
读取数据3
写入数据3
写入数据4
读取数据4
*/
非缓存通道
/*
缓冲通道可以储存一定量的数据
特点:通道满载时,发送操作被阻塞
特点:通道空时,接收操作被阻塞。
非缓存通道时收发即时阻塞的
*/
func main() {
ch := make(chan string)
go func() {
for i := 0; i < 5; i++ {
ch <- fmt.Sprintf("读取数据%v", i)
fmt.Printf("写入数据%v\n", i)
}
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
}
/*
写入数据0
读取数据0
读取数据1
写入数据1
写入数据2
读取数据2
读取数据3
写入数据3
写入数据4
读取数据4
*/
/*
缓冲通道可以储存一定量的数据
特点:通道满载时,发送操作被阻塞
特点:通道空时,接收操作被阻塞。
非缓存通道时收发即时阻塞的
*/
func main() {
ch := make(chan int, 2) // 创建一个能够缓存2个整数的通道
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
ch <- 1
fmt.Println("发送第一次数据,无阻塞")
ch <- 2
fmt.Println("发送第二次数据,无阻塞")
ch <- 3
fmt.Println("发送第三次数据,发生阻塞") // 第3次发送,超出缓冲区大小,导致阻塞
}()
wg.Wait()
}
/*
缓冲通道可以储存一定量的数据
特点:通道满载时,发送操作被阻塞
特点:通道空时,接收操作被阻塞。
非缓存通道时收发即时阻塞的
*/
func main() {
ch := make(chan int, 2) // 创建一个能够缓存2个整数的通道
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
ch <- 1
fmt.Println("发送第一次数据,无阻塞")
ch <- 2
fmt.Println("发送第二次数据,无阻塞")
}()
go func() {
defer wg.Done()
v := <-ch
fmt.Printf("第一次接收数据,无阻塞%v\n", v)
v = <-ch
fmt.Printf("第二次接收数据,无阻塞%v\n", v)
v = <-ch
fmt.Printf("第三次接收数据,发生阻塞%v\n", v)
}()
wg.Wait()
}
管道-管道阻塞
阻塞通道可以做信号传递,阻塞通道收到信号,关闭阻塞某的个goroutine。
/*
阻塞通道可以做信号传递,阻塞通道收到信号,关闭阻塞某的个goroutine。
主函数往管道发送数据,goroutine接收数据现象.goroutine 没有足够的时间打印结果,由于主函数的退出,程序终止了
解决方案:阻塞主程序
*/
func main() {
ch := make(chan int)
go func() {
result := <-ch
fmt.Println(result)
}()
ch <- 4
}
/*
主函数往管道发送数据,goroutine接收数据现象.goroutine 没有足够的时间打印结果,由于主函数的退出,程序终止了
解决方案:阻塞主程序
*/
func main() {
done := make(chan struct{})
ch := make(chan int)
go func() {
result := <-ch
fmt.Println(result)
close(done)
}()
ch <- 4
<-done
}
/*
阻塞
channel默认是阻塞的,当数据发送到channel发生阻塞,直到其他goroutine从channel中读取数据
读取也会堵塞,知道其他goroutine将数据写入改channel
阻塞
语法: <- channel变量
说明:阻塞这个变量,知道有数据进来
案例:go程接受数据,主函数发送数据阻塞变量
*/
func main() {
ch1 := make(chan int)
ch2 := make(chan bool)
go func() {
data, ok := <-ch1
time.Sleep(3 * time.Microsecond)
if ok {
fmt.Println(data)
}
ch2 <- true
}()
ch1 <- 10 //接受到数据回退出
//close(ch2) 以前关闭会导致提前退出
<-ch2 //堵塞ch2等待匿名函数运行结束,防止主函数goroutine退出导致匿名函数的coroutine提前退出
fmt.Println("main over...")
}
管道-单向管道
/*
单向管道
接收数据:ch <-chan int
发送数据:ch chan<- int
*/
func accept(ch <-chan int) {
v := <-ch
fmt.Println(v)
}
func send(ch chan<- int) {
ch <- 1
}
func main() {
ch := make(chan int)
go accept(ch)
go send(ch)
time.Sleep(time.Second)
}
管道-死锁现象
/*
死锁
并发的程序给一个管道发送和接收数据不是成对出现就会发生死锁
*/
func main() {
done := make(chan struct{})
<-done
fmt.Println("?")
}
/*
死锁
Channel发送的值的类型必须与Channel的元素类型一致。
如果接收方一直没有接收,那么发送操作将持续阻塞。
此时所有的Goroutine,包括main的Goroutine都处于等待状态。运行会提示如下错误。
*/
func main() {
c := make(chan int)
c <- 5
}
//错误:fatal error: all goroutines are asleep - deadlock!
管道-返回接受管道
// ReturnReceiveChannel 返回一个只能接收的管道。
func ReturnReceiveChannel() <-chan int {
ch := make(chan int)
go func() {
for i := 1; ; i++ {
ch <- i // 向管道发送数据
time.Sleep(1 * time.Second) // 模拟耗时操作
}
}()
return ch // 返回管道
}
func main() {
receiveCh := ReturnReceiveChannel() // 只调用一次函数
for v := range receiveCh { // 持续从同一个管道接收数据
fmt.Println(v)
}
}
管道-返回发送管道
// ReturnSendChannel 返回一个只发送管道
func ReturnSendChannel() chan<- any {
ch := make(chan any)
go func() {
for v := range ch {
fmt.Println(v)
}
}()
return ch
}
func main() {
ReturnSendChannel() <- 1
time.Sleep(1 * time.Second)
}
select语句
背景
如果有两个并发的操作,优先执行那个?我们不能偏好某个操作,否则就不能处理某些情况。
select的语句
select中每个case语句都是对一个通道的读取和写入,它从任何一个可执行的case中随机挑选。
for-select语句
for-select
语句结合了for
循环和select
语句,可以实现持续地监听通道,并根据不同通道的就绪状态执行相应的操作。selelct-default语句
select
语句可以和default
语句结合使用,default
用于在没有任何case
条件满足时执行的操作。注意:如果两个goroutine同时访问相同的通道,则必须再两个g中一相同的顺序访问两个通道,否则会发生死锁
解决方案:使用select语句(随机挑选)
注意:如果用for-select语句获取关闭的通道会一致获取默认值
解决方案:可以再case中判断管道是否关闭如果关闭把管道设置为nil就不会获取空值了
/*
select语句
select中每个case语句都是对一个通道的读取和写入,它从任何一个可执行的case中随机挑选。
*/
func main() {
ch := make(chan int)
ch1 := make(chan int)
go func() {
ch <- 1
fmt.Printf("发送数据:%v\n", 1)
}()
go func() {
r := <-ch1
fmt.Printf("接收到数据:%v\n", r)
}()
select {
case r := <-ch:
fmt.Printf("接收到数据:%v\n", r)
case ch1 <- 2:
fmt.Printf("发送数据:%v\n", 2)
}
time.Sleep(1 * time.Second)
}
for-select语句
/*
for-select语句
for-select语句结合了for循环和select语句,可以实现持续地监听通道,并根据不同通道的就绪状态执行相应的操作。
*/
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
// 向 ch1 通道发送数据 "Hello" 每隔1秒发送一次
go func() {
for {
time.Sleep(1 * time.Second)
ch1 <- "Hello"
}
}()
// 向 ch2 通道发送数据 "World" 每隔2秒发送一次
go func() {
for {
time.Sleep(2 * time.Second)
ch2 <- "World"
}
}()
// 使用 for-select 结构监听多个通道的数据
for {
select {
case msg1 := <-ch1:
fmt.Println("Received from ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("Received from ch2:", msg2)
}
}
}
select-default语句
/*
select-default语句
select语句可以和default语句结合使用,default用于在没有任何case条件满足时执行的操作。
*/
func main() {
ch := make(chan string)
// 启动一个goroutine每隔1秒向通道发送消息
go func() {
for {
time.Sleep(1 * time.Second)
ch <- "Message"
}
}()
// 使用 select 结构监听通道和 default 分支
for {
select {
case msg := <-ch:
fmt.Println("Received:", msg)
default:
fmt.Println("No message received")
time.Sleep(1 * time.Second)
}
}
}
select-随机访问
/*
如果两个g以不相同的顺序访问多个通道现象.
注意:如果两个goroutine同时访问相同的通道,则必须再两个g中一相同的顺序访问两个通道,否则会死锁。
解决方案:使用select语句(随机挑选)
*/
func main() {
ch := make(chan int)
ch1 := make(chan int)
go func() {
ch <- 1
ch1 <- 2
}()
r1 := <-ch1
r := <-ch
fmt.Println(r, r1)
}
/*
fatal error: all goroutines are asleep - deadlock!
*/
/*
如果两个g以不相同的顺序访问多个通道现象.
注意:如果两个goroutine同时访问相同的通道,则必须再两个g中一相同的顺序访问两个通道,否则会死锁。
解决方案:使用select语句(随机挑选)
*/
func main() {
ch := make(chan int)
ch1 := make(chan int)
go func() {
ch <- 1
ch1 <- 2
}()
select {
case r := <-ch:
fmt.Println(r)
case r := <-ch1:
fmt.Println(r)
}
}
select-跳过无用管道
/*
如果用for-select语句获取关闭的通道会一致获取默认值
select跳过无用通道
可以再case中判断管道是否关闭如果关闭把管道设置为nil就不会获取空值了
*/
func main() {
ch := make(chan int)
close(ch)
for {
select {
case v := <-ch:
fmt.Println(v)
}
}
}
/*
select跳过无用通道
可以再case中判断管道是否关闭如果关闭把管道设置为nil就不会获取空值了
*/
func main() {
ch := make(chan int)
ch1 := make(chan int)
go func() {
for {
time.Sleep(time.Second)
ch <- 2
}
}()
go func() {
ch1 <- 1
close(ch1)
}()
for {
select {
case v, ok := <-ch:
if !ok {
ch = nil
} else {
fmt.Println(v)
}
case v, ok := <-ch1:
if !ok {
ch1 = nil
} else {
fmt.Println(v)
}
}
}
}
select-阻塞
/*
使用select的阻塞机制
操作
*/
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
time.Sleep(10 * time.Microsecond)
data := <-ch1
fmt.Println("ch1", data)
}()
go func() {
time.Sleep(2 * time.Microsecond)
data := <-ch2
fmt.Println("ch2", data)
}()
select {
case ch1 <- 100:
close(ch1)
fmt.Println("向ch1中写入数据")
case ch2 <- 200:
close(ch2)
fmt.Println("向ch2中写入数据")
case <-time.After(2 * time.Microsecond):
fmt.Println("延迟通道")
//default:
// fmt.Println("default....")
}
time.Sleep(4 * time.Microsecond)
}
标准包sync
sync
包是 Go 语言标准库提供的用于同步并发的包。
sync.WaitGroup
/*
sync.WaitGroup同步等待组:
WaitGroup作用等待一组Goroutine结束
主g调用Add()方法设置等待g的数量,每个被等待的g结束后调用Done()方法表明减少一个g
主g调用Wait方法阻塞支所有g结束
*/
func main() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i <= 5; i++ {
go func(i int) {
defer wg.Done()
time.Sleep(5 * time.Second)
fmt.Println(i)
}(i)
}
wg.Wait()
}
sync.Mutex
/*
场景:多个并发的程序抢票,但是票的数量不能少于0,即使判断了数量也防止不了超出买票
*/
func main() {
var wg sync.WaitGroup
c := 5
wg.Add(5)
for i := 0; i < 5; i++ {
go func() {
wg.Done()
c--
c--
}()
}
wg.Wait()
fmt.Println(c)
}
/*
sync.mutex 互斥锁
*/
func main() {
var wg sync.WaitGroup
var mutex sync.Mutex
c := 5
wg.Add(5)
for i := 0; i < 5; i++ {
go func() {
wg.Done()
for i := 0; i < 2; i++ {
mutex.Lock()
if c > 0 {
c--
}
mutex.Unlock()
}
}()
}
wg.Wait()
fmt.Println(c)
}
sync.RWMutex
/*
注意:对map并发操作会导致panic
*/
func main() {
var wg sync.WaitGroup
m := make(map[int]int)
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
m[j] = j * j // 并发写入映射
}
}()
}
wg.Wait()
fmt.Println("Done")
}
/*
解决方案使用读写互斥锁给map加锁
*/
type Cache struct {
Data map[string]interface{}
rw sync.RWMutex
}
func main() {
var wg sync.WaitGroup
m := Cache{
Data: make(map[string]interface{}), // 初始化 Data 字段
}
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
m.w(fmt.Sprintf("%v", j), j*j)
}
}()
}
wg.Wait()
fmt.Println("Done")
}
func (c *Cache) r(s string) {
c.rw.RLock()
fmt.Println(c.Data[s])
c.rw.RUnlock()
}
func (c *Cache) w(s string, s1 interface{}) {
c.rw.Lock()
c.Data[s] = s1
fmt.Println(c.Data)
c.rw.Unlock()
}
sync.Pool
/*
对象重复利用 sync.Pool
sync.Pool作用
创建对象池,缓存一组对象可以重复使用,以此减少内存分配和降低GC的压力.
sync.Pool应用场景
用于连接池(数据库连接池,网络连接,grpc连接).
sync.Pool应用场景注意事项
用户缓存一些创建成本较高,使用比较频繁的对象
Pool的长度默认为CPU线程数
储存在Pool中的对象随时可能会被回收,可能再不被通知的情况下.
不建议使用的:没有什么创建成本不建议使用对象池
// Conn 重复利用的对象
// NewConn 获取新对象
// ConnPool 对象池类型
// NewConnPool 获取对象池
// Get 对象池创建对象
// Put 对象池放回对象
*/
const (
ON = 1
OFF = 0
)
// Conn 重复利用的对象
type Conn struct {
ID int64
Status int
Target string
}
// NewConn 获取新对象
func NewConn(target string) *Conn {
return &Conn{
ID: rand.Int63(),
Status: ON,
Target: target,
}
}
// ConnPool 对象池类型
type ConnPool struct {
sync.Pool
}
// GetPool 获取对象池
func GetPool(target string) (*ConnPool, error) {
return &ConnPool{
sync.Pool{
New: func() any {
return NewConn(target)
},
}}, nil
}
// Get 对象池创建对象
func (c *ConnPool) Get() *Conn {
conn := c.Pool.Get().(*Conn)
if conn.Status == OFF {
conn = c.Pool.New().(*Conn)
}
return conn
}
// Put 对象池放回对象
func (c *ConnPool) Put(conn *Conn) {
if conn.Status == OFF {
return
}
c.Pool.Put(conn)
}
func main() {
// 目标地址
target := "192.168.0.1"
// 获取连接池
pool, err := GetPool(target)
if err != nil {
log.Fatal(err)
}
// 往池中放入5个连接对象
for i := 0; i < 5; i++ {
conn := &Conn{
ID: int64(i),
Target: target,
Status: ON,
}
pool.Put(conn)
}
// 并发获取连接对象
wg := sync.WaitGroup{}
for i := 0; i < 6; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 2; j++ {
conn := pool.Get()
fmt.Println(conn.ID)
pool.Put(conn)
}
}()
}
wg.Wait()
}
sync.Map
/*
sync.map是并发安全的map
sync.map使用场景
适用于读多写少的去应用场景
再key值存在的时候,比普通map+锁性能好
*/
func main() {
m := sync.Map{}
//Store 设置键的值
m.Store("name", "张三")
m.Store("name", "李四")
//Load 通过键返回储存值,如果没有这个键则返回nil。ok表示是否找到值
fmt.Println(m.Load("name"))
fmt.Println(m.Load("age"))
//Delete 删除键
m.Delete("name")
fmt.Println(m.Load("name"))
//LoadOrStore 如果这个键不存在,则设置值、ok返回false,如果这个键存在,不设置值,ok返回true。
fmt.Println(m.LoadOrStore("age", "18"))
fmt.Println(m.LoadOrStore("age", "19"))
//LoadAndDelete 删除前查看是否存在
fmt.Println(m.LoadAndDelete("age"))
fmt.Println(m.LoadAndDelete("age"))
//Range
m.Store("name", "张三")
m.Store("age", 1)
m.Range(func(key, value any) bool {
fmt.Println(key, value)
return true
})
}
sync.Once
/*
sync.Once
sync.Once的作用:
确保某个操作只执行一次,通常用于初始化单例资源或执行仅需执行一次的操作。
sync.Once的使用场景:
1. 单例场景
2. 仅加载一次的数据懒加载场景
*/
// MapOnce 结构体包含一个 sync.Once 和一个用于存储数据的 map
type MapOnce struct {
sync.Once // 嵌入 sync.Once,利用其实现只执行一次的特性
Data map[string]int
}
// LoadData 方法用于加载数据,利用 sync.Once 确保操作只执行一次
func (m *MapOnce) LoadData() {
m.Do(func() {
list := []string{"a", "b", "c", "d"}
for _, v := range list {
_, ok := m.Data[v]
if !ok {
m.Data[v] = 0
}
m.Data[v] += 1
}
})
}
func main() {
// 创建 MapOnce 实例
m := &MapOnce{
Once: sync.Once{}, // 初始化 sync.Once
Data: make(map[string]int), // 初始化存储数据的 map
}
// 使用 WaitGroup 等待所有 goroutine 完成
wg := sync.WaitGroup{}
for i := 0; i <= 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
m.LoadData() // 并发调用 LoadData 方法
}()
}
wg.Wait() // 等待所有 goroutine 完成
// 打印加载的数据
fmt.Println(m.Data)
}
sync.Cond
func main() {
var (
mu sync.Mutex // 互斥锁,保护条件变量
cond = sync.NewCond(&mu) // 创建基于互斥锁的条件变量
boilingTemp = 100 // 烧开的温度
)
// 模拟热水壶,监测温度
go func() {
for i := 0; i <= boilingTemp; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Printf("Current temperature: %d°C\n", i)
if i == boilingTemp {
mu.Lock()
cond.Broadcast() // 达到烧开温度,通知所有等待的用户可以拿热水
mu.Unlock()
}
}
}()
// 模拟多个用户等待热水
wg := sync.WaitGroup{}
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
mu.Lock()
defer mu.Unlock()
fmt.Printf("User %d is waiting for hot water...\n", id)
cond.Wait() // 等待热水壶达到烧开温度的通知
fmt.Printf("User %d got hot water!\n", id)
}(i)
}
wg.Wait() // 等待所有用户获取热水
}
标准包time
time.After()
/*
After()函数:
After()函数相当于NewTimer(d).C
*/
func main() {
//创建计时器
ch1 := time.After(4 * time.Second)
fmt.Println(time.Now())
//接受管道中的定时后的时间
data, ok := <-ch1
fmt.Println(data, ok)
}
time.NewTimer()
/*
Timer结构体:
Timer计时器类型表示单个事件。当Timer计时器过期时,当前时间将被发送到C,
除非Timer是由After()创建的。计时器必须用NewTimer或AfterFunc创建。
NewTimer()函数:
NewTimer()创建一个新的计时器,它会在持续时间d之后将当前时间发送到channel上
*/
func main() {
//创建计时器
timer1 := time.NewTimer(4 * time.Second)
fmt.Printf("%T\n", timer1)
fmt.Println(time.Now())
//接受管道中的定时后的时间
data := <-timer1.C
fmt.Printf("%T\n", timer1.C)
fmt.Println(data)
}
/*
单次执行任务
创建计时器,在goroutine中接收channel到定时后的时间,随后执行单词任务
*/
func main() {
//创建计时器
ch1 := time.After(4 * time.Second)
fmt.Println(time.Now())
ch2 := make(chan bool)
//接受管道中的定时后的时间
go func() {
fmt.Println("并发任务直接执行")
data, ok := <-ch1
fmt.Println(data, ok)
fmt.Println("并分任务结束执行")
ch2 <- true
}()
<-ch2
time.Sleep(10 * time.Second)
fmt.Println("睡眠十秒")
}
标准包runtime
runtime包里面定义了一些协程管理相关的方法
runtime.Gosched()
让出当前协程的 CPU 时间片给其他协程。
runtime.Goexit()
退出当前协程,但是 defer 语句会照常执行。
func main() {
//创建一个goroutine
go func() {
defer fmt.Println("我不爱你")
//终止当前goroutine
runtime.Goexit()
fmt.Println("我爱你") //不会执行
}()
for {
time.Sleep(1 * time.Second)
}
}
runtime.GOMAXPROCS()
func main() {
//n:=runtime.GOMAXPROCS(1)
n := runtime.GOMAXPROCS(2)
fmt.Printf("cpu的核数:%v", n)
for {
go fmt.Println("a")
fmt.Println("b")
}
}
设置goroutine可以使用的cpu核数。
Golang 默认所有任务都运行在一个 cpu核里。
runtime.NumCPU()
返回当前系统的 CPU 核数量。
runtime.GOROOT()
获取 goroot 目录。
runtime.GOOS
查看目标操作系统。很多时候,我们会根据平台的不同实现不同的操作,就可以用GOOS来查看自己所在的操作系统。
runtime.GC
会让运行时系统进行一次强制性的垃圾收集
runtime.NumGoroutine
返回正在执行和排队的任务总数。
并发解决方案
通道结束模式
/*
通道结束模式
通道结束模式提供一种方法来通知goroutine停止,它用通道发送信号。
*/
// searchData 函数接收一个字符串和多个函数,将该字符串传递给这些函数,并返回最先完成执行的函数结果
func searchData(s string, searchFuncS []func(string) string) string {
// 创建一个用于信号通知的 done 通道
done := make(chan struct{})
// 创建一个用于存放结果的通道
result := make(chan string)
// 遍历所有给定的搜索函数
for _, searchFunc := range searchFuncS {
go func(searchFunc func(string) string) {
// 使用 select 语句在 result 通道上执行非阻塞发送或 done 通道的接收
select {
case result <- searchFunc(s): // 尝试发送 searchFunc 的结果到 result 通道
case <-done: // 如果 done 通道被关闭,直接结束
}
}(searchFunc) // 传递每个搜索函数的副本,避免闭包中的竞态条件
}
r := <-result // 等待第一个搜索函数完成并返回结果
close(done) // 关闭 done 通道,通知其他 goroutines 停止
return r // 返回第一个完成的搜索函数的结果
}
// f 模拟一个搜索函数,执行耗时 1 秒
func f(s string) string {
time.Sleep(1 * time.Second)
return "我恨你"
}
// f1 模拟另一个搜索函数,执行耗时 2 秒
func f1(s string) string {
time.Sleep(2 * time.Second)
return "我爱你"
}
func main() {
// 使用 searchData 函数调用 f 和 f1 函数,并打印返回的结果
result := searchData("", []func(s string) string{f, f1})
fmt.Println(result)
}
背压
被压技术可以限制并发请求数量
给任务限定时间
/*
如何处理超时
大多数交互程序一般要求再定时间内返回相应.
*/
func f() (result int, err error) {
time.Sleep(time.Second)
return 3, nil
}
func TimeoutStop() (result int, err error) {
done := make(chan struct{})
go func() {
result, err = f()
close(done)
}()
select {
case <-done:
return result, err
case <-time.After(time.Second):
return 0, errors.New("超时了")
}
}
func main() {
fmt.Println(TimeoutStop())
}
确保方法只能有一个协程在执行
要确保一个方法在任何时候只被一个协程执行,可以使用 sync.Mutex。互斥锁可以阻止多个协程同时执行同一块代码区域。
type Singleton struct {
mu sync.Mutex
}
// ExclusiveMethod 是需要被互斥执行的方法
func (s *Singleton) ExclusiveMethod() {
s.mu.Lock()
defer s.mu.Unlock()
// 此区域的代码在同一时刻只能被一个协程执行
fmt.Println("方法开始执行")
time.Sleep(2 * time.Second) // 假设执行需要一些时间
fmt.Println("方法执行完毕")
}
func main() {
s := Singleton{}
// 启动多个协程尝试调用方法
for i := 0; i < 3; i++ {
go s.ExclusiveMethod()
}
time.Sleep(10 * time.Second) // 等待足够时间,确保所有协程执行完成
}
package main
import (
"fmt"
"sync"
"time"
)
type Data struct {
Value int
mu sync.Mutex
}
func (d *Data) Increment() {
d.mu.Lock()
d.Value++
d.mu.Unlock()
}
func (d *Data) PrintValue() {
d.mu.Lock()
fmt.Println("Current Value:", d.Value)
d.mu.Unlock()
}
func main() {
var wg sync.WaitGroup
data := Data{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
data.Increment()
}()
}
wg.Wait()
data.PrintValue()
}
使用一个布尔标志和 sync.Mutex 来追踪和控制资源的锁定状态
package main
import (
"fmt"
"sync"
)
type LockableResource struct {
mu sync.Mutex
isLocked bool
}
func (r *LockableResource) Lock() {
r.mu.Lock()
r.isLocked = true
}
func (r *LockableResource) Unlock() {
r.isLocked = false
r.mu.Unlock()
}
func (r *LockableResource) IsLocked() bool {
return r.isLocked
}
func main() {
resource := LockableResource{}
resource.Lock()
fmt.Println("Resource locked:", resource.IsLocked()) // 输出: Resource locked: true
resource.Unlock()
fmt.Println("Resource locked:", resource.IsLocked()) // 输出: Resource locked: false
}
package main
import (
"fmt"
"sync"
"time"
)
type LockableResource struct {
value int
mu sync.Mutex
lockChan chan bool // Channel to notify when resource is locked
unlockChan chan bool // Channel to notify when resource is unlocked
}
func NewLockableResource() *LockableResource {
return &LockableResource{
lockChan: make(chan bool, 1), // Buffered channel
unlockChan: make(chan bool, 1),
}
}
// Lock locks the resource and notifies the status
func (lr *LockableResource) Lock() {
lr.mu.Lock()
fmt.Println("Resource locked")
lr.lockChan <- true
}
// Unlock unlocks the resource and notifies the status
func (lr *LockableResource) Unlock() {
lr.mu.Unlock()
fmt.Println("Resource unlocked")
lr.unlockChan <- true
}
// UseResource simulates using the resource
func (lr *LockableResource) UseResource() {
lr.Lock()
time.Sleep(1 * time.Second) // Simulating work
lr.Unlock()
}
func main() {
resource := NewLockableResource()
go resource.UseResource()
// Monitoring lock and unlock events
for i := 0; i < 2; i++ {
select {
case <-resource.lockChan:
fmt.Println("Received lock notification")
case <-resource.unlockChan:
fmt.Println("Received unlock notification")
}
}
}
type s struct {
num int
}
var s1 s
var lock sync.Mutex // 全局互斥锁
func main() {
for i := 0; i < 2; i++ {
go f()
}
time.Sleep(20 * time.Second)
}
func f() {
lock.Lock() // 使用全局互斥锁
defer lock.Unlock()
time.Sleep(5 * time.Second) // 模拟耗时操作
s1.num++
fmt.Println(s1.num)
}
解耦接收方和消费方
/*
解耦生产方和消费方
*/
func main() {
ch := make(chan int)
var wg sync.WaitGroup
wg.Add(2)
//生产方
go func() {
defer wg.Done()
for i := 0; i <= 10; i++ {
ch <- i
}
close(ch)
}()
//消费方
go func() {
defer wg.Done()
for v := range ch {
fmt.Println(v)
}
}()
wg.Wait()
}
/*
解耦生产方和消费方
*/
// 消费者函数
func worker(tasksChan <-chan int) {
for task := range tasksChan {
fmt.Printf("处理任务 %d\n", task)
time.Sleep(time.Second) // 模拟任务处理时间
}
}
// 生产者
func produce(tasksChan chan<- int) {
tasks := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for _, task := range tasks {
tasksChan <- task
}
close(tasksChan) // 数据发送完毕,关闭通道
}
func main() {
ch := make(chan int)
go produce(ch)
worker(ch)
}
控制并发数
/*
如何控制并发数
1.使用有缓冲Channel控制并发数。这种方法使用了通道的阻塞特性。
*/
func main() {
var wg sync.WaitGroup
ch := make(chan int, 3) // 控制并发数为3
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) { // 正确地将当前的循环变量i作为参数传递给goroutine
ch <- 1 // 请求权限执行
// 模拟工作时长
time.Sleep(5 * time.Second)
fmt.Println(i)
<-ch // 完成工作,释放位置
wg.Done()
}(i)
}
wg.Wait() // 等待所有goroutine完成
}
/*
如何控制并发数
2.使用sync.waitGroup控制并发数
*/
func main() {
//一个goroutine不断往管道发送数据
ch := make(chan int)
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch) //发送完成要关闭管道
fmt.Println("关闭管道")
}()
//使用sync.waitGroup开启两个Goroutine不断的接收管道的数据
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for v := range ch {
fmt.Printf("处理任务 %v\n", v)
time.Sleep(2 * time.Second) // 模拟任务处理时间
}
}()
}
wg.Wait()
}
/*
如何控制并发数
2.使用sync.waitGroup控制并发数
*/
// 生产者
func worker(tasksChan <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for task := range tasksChan {
fmt.Printf("处理任务 %d\n", task)
time.Sleep(time.Second) // 模拟任务处理时间
}
}
// 消费者
func consume(tasksChan chan<- int) {
tasks := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for _, task := range tasks {
tasksChan <- task
}
close(tasksChan) // 数据发送完毕,关闭通道
}
func main() {
ch := make(chan int)
go consume(ch) //启动生产者
var wg sync.WaitGroup //启动生产者
const n = 5
wg.Add(n)
for i := 0; i < n; i++ {
go worker(ch, &wg) // 传递wg的地址
}
wg.Wait() // 等待所有worker完成
}
/*
如何控制并发数
2.使用sync.waitGroup控制并发数
*/
func worker(taskCh <-chan int) {
const n = 5
for i := 0; i <= n; i++ {
go func(id int) {
for {
task := <-taskCh
fmt.Printf("finish task:%v, worker:%v\n", task, id)
time.Sleep(2 * time.Second)
}
}(i)
}
}
func main() {
taskCh := make(chan int)
go worker(taskCh)
for i := 0; i <= 50; i++ {
taskCh <- i
}
time.Sleep(1 * time.Hour)
}
超时控制
/*
超时控制 某个任务超时,则停止任务
实现方案:用select_case语句接受time.after函数返回的channel数据
*/
//异步操作,将结果发送到管道
func task(ch chan int) {
time.Sleep(2 * time.Second)
ch <- 1
}
func main() {
ch := make(chan int)
go task(ch)
select {
case <-time.After(4 * time.Second):
fmt.Println("任务超时,执行失败")
case v := <-ch:
fmt.Printf("任务未超时,执行成功,获取结果是:%v", v)
}
}
定时任务
/*
定时任务 按照周期执行某个任务
实现方案:用for无限循环select语句,接受一个定时器
*/
func task() {
fmt.Println("Task executed", time.Now())
}
func main() {
// 创建一个定时器,设置周期为1秒
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop() // 确保在不再需要时停止定时器
// 使用for循环和select语句监听定时器的信号
for {
select {
case <-ticker.C:
// 定时器到期,执行任务
task()
// 可以添加更多的case来处理其他信号,例如程序退出信号
}
}
}
停止信号
/*
停止信号 例如停止某个定时器
*/
func task() {
fmt.Println("Task executed", time.Now())
}
func main() {
// 创建一个定时器,设置周期为1秒
ticker := time.NewTicker(1 * time.Second)
// 创建一个停止信号的通道
stopChan := make(chan bool)
go func() {
for {
select {
case <-ticker.C:
// 定时器到期,执行任务
task()
case <-stopChan:
// 接收到停止信号,停止执行任务
fmt.Println("Stopped task execution")
return
}
}
}()
// 模拟运行一段时间后停止任务
time.Sleep(5 * time.Second)
stopChan <- true // 发送停止信号
ticker.Stop() // 停止定时器
// 等待一段时间以确保goroutine已经完全停止
time.Sleep(1 * time.Second)
fmt.Println("Main function completed")
}
// 案例需求:十个用户获取信息,一个用户需要一秒,使用两个Goroutine
// 解决方案:使用有缓存Channel控制并发数
// 通过ID获取这些用户的信息
func task(ID int) {
time.Sleep(1 * time.Second)
fmt.Printf("这是用户%v的信息\n", ID)
}
func f() {
startTime := time.Now()
//模拟获取到的用户id
sli := []int{}
for i := 1; i <= 10; i++ {
sli = append(sli, i)
}
//启动有缓存的Channel控制并发量
var wg sync.WaitGroup
ch := make(chan int, 2)
for _, v := range sli {
wg.Add(1)
go func() {
defer wg.Done()
ch <- 1
task(v)
<-ch
}()
}
wg.Wait()
fmt.Println(time.Now().Sub(startTime))
}
func main() {
f()
}
// 如果对未初始化的Channel.启动goroutine发送数据和接受数据都会阻塞.
func main() {
fmt.Println("当前goroutine的数量:", runtime.NumGoroutine())
f()
fmt.Println("当前goroutine的数量:", runtime.NumGoroutine())
}
func f() {
var ch chan int
for i := 0; i < 10; i++ {
go func() {
ch <- 1
}()
}
}
// 使用chancel发送不接受,接受不发送
func main() {
fmt.Println("当前goroutine的数量:", runtime.NumGoroutine())
f()
fmt.Println("当前goroutine的数量:", runtime.NumGoroutine())
}
func f() {
ch := make(chan int)
for i := 0; i < 10; i++ {
go func() {
ch <- 1
}()
}
}
// 访问http请求,没有关闭请求.
func main() {
fmt.Println("当前goroutine的数量:", runtime.NumGoroutine())
block()
wg.Wait()
fmt.Println("当前goroutine的数量:", runtime.NumGoroutine())
}
var wg sync.WaitGroup
func block() {
for i := 0; i <= 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
f1()
}()
}
}
func f() {
_, err := http.Get("https://www.baidu.com")
if err != nil {
return
}
}
func f1() {
resp, err := http.Get("www.baidu.com")
if err != nil {
return
}
defer resp.Body.Close()
}
首先讲述了并发和并行的概念,
然后讲述了gpm的调度器行为,
然后讲述了竞争状态,
接着讲述了锁住共享资源,竞争状态导致的问题如何解决?
并发和并行
gpm的调度器行为
goroutine是由gpm调度器的调度和管理的,合理的把goroutine分配给cpu,可以通过设置分配几个逻辑处理器给调度器使用。
当goroutine占用时间过长时,调度器会停止当前正运行的goroutine,并给其他可运行的goroutine运行的机会。
竞争状态
多个goroutine访问共享资源时候同时读和写同个资源,就会导致相互竞争的状态,会十分容易引起潜在问题。
程序展示如何造成竞争状态:比如两个goroutine同时读取修改一个全局变量,执行加一操作,结果可能是1。
如果两个或者多个goroutine在没有互相同步的情况下,访问某个共享的资源,并试图同时读和写这个资源,就处于相互竞争的状态,十分容易引起潜在问题,这种情况被称作竞争状态(race candition)。
同一时刻只能有一个goroutine对共享资源进行读和写操作。锁住共享资源(解决竞争状态)
公共资源加锁的方法由atomic的的原子函数和sync的互斥锁。
goroutine和gpm的调度器行为
goroutine是由gpm调度器的调度和管理的,合理的把goroutine分配给cpu,可以通过设置分配几个逻辑处理器给调度器使用。
当goroutine占用时间过长时,调度器会停止当前正运行的goroutine,并给其他可运行的goroutine运行的机会。
package main
//这个示例程序展示如何创建goroutine,以及goroutine调度器的行为
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
runtime.GOMAXPROCS(1)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 1; i <= 3; i++ {
fmt.Println(i)
}
}()
go func() {
defer wg.Done()
for i := 11; i <= 13; i++ {
fmt.Println(i)
}
}()
wg.Wait()
}
package main
//这个示例程序展示goroutine调度器是如何在单个线程上切分时间片的
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
runtime.GOMAXPROCS(1)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 1; i <= 3; i++ {
time.Sleep(1 * time.Second)
fmt.Println(i)
}
}()
go func() {
defer wg.Done()
for i := 11; i <= 13; i++ {
time.Sleep(1 * time.Second)
fmt.Println(i)
}
}()
wg.Wait()
}