channel(管道)以及互斥锁
[toc]
😶🌫️go语言官方编程指南:https://golang.org/#
go语言的官方文档学习笔记很全,推荐去官网学习
😶🌫️我的学习笔记:github: https://github.com/3293172751/golang-rearn
区块链技术(也称之为分布式账本技术),是一种互联网数据库技术,其特点是去中心化,公开透明,让每一个人均可参与的数据库记录
❤️💕💕关于区块链技术,可以关注我,共同学习更多的区块链技术。博客http://nsddd.top
不同的协程如何通信?
方法:
- 全局变量加锁同步
- channel
因为没有对全局变量加锁,因此会出现资源争夺的问题,代码会出现错误,此时要解决的话可以加入互斥锁
time.Sleep(10*time.Second)
lock.Lock()
for k,v := range m{
fmt.Printf(%d != %v\n",k,v)
}
lock.Unlock()
⬇️ 或许你还可以使用管道
channel(管道)
演示管道使用
channel初始化:
var intChan chan int
intChan = make(chan int,10)
管道是引用数据类型
一定要使用make不然会报错
package main
import(
"fmt"
)
func main(){
/*创建一个可以存放3个int类型的管道*/
var intChan chan int
intChan = make(chan int ,3)
//看一下intchan是一种什么类型,地址还是数字
fmt.Println("intchan 本身的值为:",intchan)
fmt.Printf("intchan 本身的地址为%p:",&intchan)
}
看下intChan是什么
[root@mail ~]# go run chan.go
intchan 本身的值为: 0xc00001e080
intchan 本身的地址:0xc00000e028
由此可见,管道是一个地址
管道的基本操作
💡简单的一个案例如下:
/*
* @Description:channel管道
* @Author: xiongxinwei 3293172751nss@gmail.com
* @Date: 2022-10-04 21:37:41
* @LastEditTime: 2022-10-25 14:39:30
* @FilePath: \code\go-super\37-main.go
* @Github_Address: https://github.com/cubxxw/awesome-cs-cloudnative-blockchain
* Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
*/
package main
import (
"fmt"
)
func main() {
//创建一个管道
ch := make(chan int, 10) //管道的容量是10
//给管l道中写入数据
ch <- 10
ch <- 20
ch <- 30
ch <- 40
fmt.Println("len(ch) = ", len(ch))
fmt.Println("cap(ch) = ", cap(ch))
fmt.Println("ch = ", ch)
//从管道中读取数据
num := <-ch
fmt.Println("num = ", num) //10
fmt.Println("再读取一个数据", <-ch) //20
fmt.Println("再读取一个数据", <-ch) //30
fmt.Println("再读取一个数据", <-ch) //40
//fmt.Println("再读取一个数据", <-ch) //error: all goroutines are asleep - deadlock!
//继续批量存储数据
ch <- 50
ch <- 150
ch <- 250
fmt.Println("len(ch) = ", len(ch))
fmt.Println("cap(ch) = ", cap(ch))
fmt.Println("ch = ", ch)
//遍历管道
for i := 0; i < len(ch)+1; i++ {
num = <-ch
fmt.Println("num = ", num)
}
}
🚀 编译结果如下:
[Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\37-main.go"
len(ch) = 4
cap(ch) = 10
ch = 0xc000110000
num = 10
再读取一个数据 20
再读取一个数据 30
再读取一个数据 40
len(ch) = 3
cap(ch) = 10
ch = 0xc000110000
num = 50
num = 150
注意 ⚠️:
fmt.Println("再读取一个数据", <-ch)
:error: all goroutines are asleep - deadlock!
管道阻塞引起死锁,所以一定要注意管道容量~
遍历管道,我们发现上面的后面加入250
并没有打印
for循环遍历管道的时候管道可以不关闭,但是for-range必须要关闭,但是我的for遍历好像会出现数据丢失,不知道什么原因
for i := 0; i < len(ch); i++ {
fmt.Println("i = ", <-ch)
}
或许我们可以使用for-range
遍历:
- 再此之前,你需要使用
close(ch)
关闭管道 - 管道里面是没有
key
//遍历管道
for v := range ch {
fmt.Println("v = ", v)
}
只使用for也是一样的:
不能关闭管道,不然没法取出
//关闭unbuffered channel
close(ch)
for {
val, ok := <-ch
if !ok {
fmt.Println("读取完毕")
break
} else {
fmt.Println("var = ", val) //不断读取管道中的数据
}
给定make值,因为len(ch)长度会变化:
for i := 0; i <=2; i++ {
fmt.Println("i = ", <-ch)
}
💡 上面的方法不需要关闭管道。
截图案例
管道是一个应用类型,使用函数变化的是地址
向管道写入数据
/*************************************************************************
> File Name: chan.go
> Author: smile
> Mail: 3293172751nss@gmail.com
> Created Time: Sun 20 Mar 2022 11:40:41 AM CST
************************************************************************/
package main
import(
"fmt"
)
func main(){
/*创建一个可以存放3个int类型的管道*/
var intChan chan int
intChan = make(chan int ,3)
//看一下intchan是一种什么类型,地址还是数字
fmt.Println("intchan 本身的值为:",intChan)
fmt.Printf("intchan 本身的地址为%p:\n",&intChan)
//向管道中写入数据--注意使用的是<- 写入符号
intChan <- 10
num := 211 //定义一个变量并且写入变量
intChan <- num
/*注意 -- 当我们给管道写入数据的时候,不能超过其容量 ,此时只能写入一条数据了,因为长度不可以比容量高,最多为3*/
intChan <- a:=100 //极限。此时再加入报错
//看看管道的长度和容量cap(容量)
fmt.Println("channel len = %v\n cap = %v\n",len(intChan),cap(intChan))
}
编译
[root@mail ~]# go run chan.go
intchan 本身的值为: 0xc0000aa000
intchan 本身的地址为0xc0000a4018:
channel len = 3
cap = 3
所以在使用管道的时候不可以超过最大的容量,可以将管道中的数据取出来后再插入,取出数据后管道长度会发生变化,但是它的容量map是不会发生变化的
案例
//取出数据
var num2 int
num2 = <-intChan
fmt.Println("num2 = ",num2)
fmt.Printf("channel len = %v\n cap = %v\n",len(intChan),cap(intChan))
num2 = <-intChan //注意如果数据全部取出,再无休止的取出会报错
fmt.Println("num2 = ",num2)
fmt.Printf("channel len = %v\n cap = %v\n",len(intChan),cap(intChan))
编译
[root@mail ~]# go run chan.go
intchan 本身的值为: 0xc0000aa000
intchan 本身的地址:0xc0000a4018
channel len = 3
cap = 3
num2 = 10
channel len = 2
cap = 3
num2 = 211
channel len = 1
cap = 3
管道的数据可以直接扔掉,没有接收的变量
<- intChan //直接扔掉
chan底层分析
进入chan底层分析
如果有一个需求,希望管道既可以放结构体,又可以放指针,即可以放入任何类型变量
此时我们可以定义一个空接口,空接口可以接收任何类型的
var allChan chan interface{} //空接口
allChan = make(chan inerface{},10)
channel关闭
使用内置函数close可以关闭channel,关闭后,只能读取数据而不能写入数据
package main
import(
"fmt"
)
func main(){
/*创建一个可以存放3个int类型的管道*/
intChan := make(chan int ,3)
intChan <- 100
intChan <- 300
close(intChan) //close关闭管道
/*
intChan -< 100
不可以再写入会报错*/
a := <-intChan
//看一下intchan是一种什么类型,地址还是数字
fmt.Println("a=",a)
}
🚀 编译结果如下:
a= 100
channel的遍历
channel的遍历只能使用for-range
遍历,不可以使用普通的for循环,因为长度会变化,当然你可以给定make的固定值。
情况:
- 遍历时,channel没有关闭,出现deadlock的错误
- 遍历时,channel已经关闭,则会正常遍历,遍历完成,就退出遍历
package main
import(
"fmt"
)
func main(){
/*创建一个可以存放100个int类型的管道,遍历*/
intChan := make(chan int ,100)
for i:=0;i<100;1++{
intChan <- 100*i //放入一百个数据到管道
}
close(intChan) //close关闭管道
//管道遍历
/* for i:=0; i<len(intChan2);i++{
不能使用该方法遍历!!!!!!!!!!
}*/
for v:=range intChan{
fmt.Println("第"+"数=",v)
}
}
goroutine 和 channel结合
/*************************************************************************
> File Name: jiehe.go
> Author: smile
> Mail: 3293172751nss@gmail.com
> Created Time: Sun 20 Mar 2022 02:09:34 PM CST
************************************************************************/
package main
import(
"fmt"
"time"
)
func writeData(intChan chan int){
for i := 1;i<=50;i++{
//放入数据
intChan <- i*i
fmt.Println("writeData",i)
//写的时候休眠一秒钟
time.Sleep(time.Second)
}
close(intChan)
}
func readData(intChan chan int,exitChan chan bool){
for{
v,ok := <-intChan
if !ok{
break
}
fmt.Println("读取到一个·数据",v)
//读取的时候休眠一秒钟
time.Sleep(time.Second)
}
//读取数据后任务完成
exitChan <- true
close(exitChan)
}
func main(){
//创建A两个管道
intChan := make(chan int,50)
exitChan := make(chan bool,1)
go writeData(intChan)
go readData(intChan,exitChan)
time.Sleep(time.Second * 10)
}
编译
[root@mail ~]# go run jiehe.go
writeData 1
读取到一个·数据 1
writeData 2
读取到一个·数据 4
writeData 3
读取到一个·数据 9
writeData 4
读取到一个·数据 16
writeData 5
读取到一个·数据 25
writeData 6
读取到一个·数据 36
writeData 7
读取到一个·数据 49
writeData 8
读取到一个·数据 64
^Csignal: interrupt
当我们make的管道容量很小,但是存入的数据很多,那么此时会出现诸塞
统计素数
我们回到开始的那一个问题,一个需求,统计
1~80000
中有哪些素数我们当时想到的方法是将统计素数的任务分配给4个CPU去做(我只有4个并行,用8个并发
主要思路
package main
import (
"fmt"
"time"
)
//向 intChan放入 1-80000个数
func putNum(intChan chan int) {
for i := 1; i <= 800000; i++ {
intChan<- i
}
//关闭intChan
close(intChan)
}
// 从 intChan取出数据,并判断是否为素数,如果是,就
// //放入到primeChan
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
//使用for 循环
// var num int
var flag bool //
for {
//time.Sleep(time.Millisecond * 10)
num, ok := <-intChan //intChan 取不到..
if !ok {
break
}
flag = true //假设是素数
//判断num是不是素数
for i := 2; i < num; i++ {
if num % i == 0 {//说明该num不是素数
flag = false
break
}
}
if flag {
//将这个数就放入到primeChan
primeChan<- num
}
}
fmt.Println("有一个primeNum 协程因为取不到数据,退出")
//这里我们还不能关闭 primeChan
//向 exitChan 写入true
exitChan<- true
}
func main() {
intChan := make(chan int , 1000)
primeChan := make(chan int, 20000)//放入结果
//标识退出的管道
exitChan := make(chan bool, 8) // 4个
start := time.Now().Unix()
//开启一个协程,向 intChan放入 1-80000个数
go putNum(intChan)
//开启4个协程,从 intChan取出数据,并判断是否为素数,如果是,就
//放入到primeChan
for i := 0; i < 8; i++ {
go primeNum(intChan, primeChan, exitChan)
}
//这里我们主线程,进行处理
//直接
go func(){
for i := 0; i < 8; i++ {
<-exitChan
}
end := time.Now().Unix()
fmt.Println("使用协程耗时=", end - start)
//当我们从exitChan 取出了4个结果,就可以放心的关闭 prprimeChan
close(primeChan)
}()
//遍历我们的 primeChan ,把结果取出
for {
_, ok := <-primeChan
if !ok{
break
}
//将结果输出
// fmt.Printf("素数=%d\n", res)
}
fmt.Println("main线程退出")
}
编译
[root@mail ~]# ./sushu
有一个primeNum 协程因为取不到数据,退出
有一个primeNum 协程因为取不到数据,退出
有一个primeNum 协程因为取不到数据,退出
有一个primeNum 协程因为取不到数据,退出
有一个primeNum 协程因为取不到数据,退出
有一个primeNum 协程因为取不到数据,退出
有一个primeNum 协程因为取不到数据,退出
有一个primeNum 协程因为取不到数据,退出
使用协程耗时= 2
main线程退出
channel使用细节
单向管道
💡简单的一个案例如下:
/*
* @Description:go 管道
* @Author: xiongxinwei 3293172751nss@gmail.com
* @Date: 2022-10-04 21:37:41
* @LastEditTime: 2022-10-25 16:55:49
* @FilePath: \code\go-super\39-main.go
* @Github_Address: https://github.com/cubxxw/awesome-cs-cloudnative-blockchain
* Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
*/
package main
import (
"fmt"
)
func main() {
ch1 := make(chan string, 3)
ch1 <- "a" //写入管道
a := <-ch1 //读取管道
fmt.Println("读取管道", a)
ch2 := make(chan<- string, 3) //管道只能写入
ch2 <- "b" //写入管道
// b := <-ch2 //读取管道
// fmt.Println("读取管道", b) //读取管道
ch3 := make(<-chan string, 3) //管道只能读取
// ch3 <- "c" //写入管道
c := <-ch3 //读取管道
fmt.Println("读取管道", c) //读取管道
}
🚀 编译结果如下:
[Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\39-main.go"
读取管道 a
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
d:/文档/最近的/awesome-golang/docs/code/go-super/39-main.go:29 +0x106
exit status 2
管道可以声明为只读或者只写,默认情况下,管道是即可读,也可以写入
package main
import (
"fmt"
"time"
)
func main(){
//默认可读可写
var chan0 chan<- int
//只写
var chan1 chan <- int
chan1 = maker(chan int,3)
//只读
var chan2 <- chan int
num2 := < chan2 //只读
}
作用范围:
func main(){
ch := make(chan int,10)
exitChan := make(chan sturct{},2)
go send(ch,exitChan) //只写
go recv(ch,exitChan) //只读
}
而且Go语言在底层做了优化,所以效率更高一些
::: details💡简单的一个案例如下: 管道的只读和只写操作定义
/*
* @Description:go只读只写管道
* @Author: xiongxinwei 3293172751nss@gmail.com
* @Date: 2022-10-04 21:37:41
* @LastEditTime: 2022-10-25 17:16:21
* @FilePath: \code\go-super\40-main.go
* @Github_Address: https://github.com/cubxxw/awesome-cs-cloudnative-blockchain
* Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
*/
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
// 只读管道
func readChan(ch <-chan int) {
for {
num := <-ch
if num == 0 {
break
}
fmt.Println(num)
time.Sleep(1000 * time.Millisecond)
}
wg.Done()
}
// 只写管道
func writeChan(ch chan<- int) {
for i := 1; i <= 100; i++ {
ch <- i
time.Sleep(time.Millisecond * 1000) // 100毫秒就是0.1秒
}
close(ch)
wg.Done()
}
func main() {
var ch = make(chan int, 1000)
wg.Add(1)
go writeChan(ch) // 只写
wg.Add(1)
go readChan(ch) // 只读
wg.Wait()
}
🚀 编译结果如下:
[Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\40-main.go"
1
2
3
4
5
6
7
8
9
.....
:::
select多路复用
select多路复用
在我们实际开发中,可能不好确定什么时候关闭该管道,此时我们就不能用close,可以使用select解决方法
注意:使用多路复用的时候不需要关闭chan
select{
case v:= <-管道
....
default:
语句
}
多路复用的解决方案
在某些情况下我们需要在多个管道中取出数据:
/*
* @Description:select 多路复用
* @Author: xiongxinwei 3293172751nss@gmail.com
* @Date: 2022-10-04 21:37:41
* @LastEditTime: 2022-10-25 17:29:28
* @FilePath: \code\go-super\41-main.go
* @Github_Address: https://github.com/cubxxw/awesome-cs-cloudnative-blockchain
* Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
*/
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
//int和int64的区别: int是根据操作系统的位数来决定的,32位系统就是int32,64位系统就是int64
ch1 := make(chan int, 100)
ch2 := make(chan int, 100)
//循环输入管道
for i := 0; i < 100; i++ {
ch1 <- i
ch2 <- i
}
wg.Add(1)
go func() {
for {
select {
case v := <-ch1:
fmt.Println("读取管道1", v)
case v := <-ch2:
fmt.Println("读取管道2", v)
}
}
wg.Done()
}()
time.Sleep(time.Second * 3)
wg.Wait()
}
🚀 编译结果如下:
[Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\41-main.go"
读取管道2 0
读取管道1 0
读取管道1 1
读取管道2 1
读取管道2 2
读取管道2 3
读取管道1 2
读取管道2 4
......
读取管道1 15
读取管道1 16
读取管道2 26
读取管道1 17
读取管道2 27
读取管道2 28
读取管道2 29
读取管道1 18
读取管道1 19
读取管道2 30
读取管道2 31
读取管道1 20
读取管道2 32
读取管道1 21
读取管道1 22
读取管道2 33
读取管道1 23
读取管道2 34
.....
读取管道1 98
读取管道1 99
💡简单的一个案例如下:
/*前面有两个管道*/
label //标签,重新读取数据
for{
select{
case v:= <-intChan :
fmt.Prinf("从intChan管道中取出数据%d\n",v)
case v:= <-stringChan :
fmt.Prinf("从stringChan管道中取出数据%d\n",v)
default:
fmt.Prinf("都取不到了,加入业务逻辑,加入或者")
breaK
/*return : 代表跳出这个函数*/
//或者使用标签
/* break label */
}
}
协程的panic处理
💡简单的一个案例如下:
/*
* @Description:Go语言协程异常处理
* @Author: xiongxinwei 3293172751nss@gmail.com
* @Date: 2022-10-04 21:37:41
* @LastEditTime: 2022-10-25 17:50:10
* @FilePath: \code\go-super\44-main.go
* @Github_Address: https://github.com/cubxxw/awesome-cs-cloudnative-blockchain
* Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
*/
package main
import (
"fmt"
"time"
)
func test() {
defer func() {
if err := recover(); err != nil {
fmt.Println("协程出现异常:", err)
}
}()
var a []int
a[0] = 100 // 这里会出现数组越界异常
}
func test2() {
defer func() {
if err := recover(); err != nil {
fmt.Println("协程出现异常:", err)
}
}()
var mapping_1 map[int]string
mapping_1[1] = "hello" // 这里会出现空指针异常
}
func main() {
go test()
go test2()
time.Sleep(time.Second * 3)
}
🚀 编译结果如下:
[Running] go run "d:\文档\最近的\awesome-golang\docs\code\go-super\44-main.go"
协程出现异常: assignment to entry in nil map
协程出现异常: runtime error: index out of range [0] with length 0
📜 对上面的解释:
协程出现异常:赋值给空映射中的条目(数组越界)
协程出现异常:运行时错误:索引超出范围[0],长度为0
互斥锁
资源竞争问题
我们可能会使用协程同时操作count
出现资源竞争的问题:
/*
* @Description:资源竞争死锁
* @Author: xiongxinwei 3293172751nss@gmail.com
* @Date: 2022-10-04 21:37:41
* @LastEditTime: 2022-10-25 18:13:35
* @FilePath: \code\go-super\45-main.go
* @Github_Address: https://github.com/cubxxw/awesome-cs-cloudnative-blockchain
* Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
*/
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
var count int
func test() {
count++
fmt.Println("the count is ", count)
time.Sleep(100 * time.Millisecond)
wg.Done()
}
func test2() {
count++
fmt.Println("the count is ", count)
time.Sleep(100 * time.Millisecond)
wg.Done()
}
func main() {
for i := 0; i < 100; i++ {
wg.Add(1)
go test()
go test2()
}
wg.Wait()
}
根据不同的编译方式编译结果不同
直接build🚀 编译结果如下:
the count is 1
the count is 2
the count is 26
the count is 4
the count is 5
the count is 6
the count is 7
the count is 8
the count is 9
the count is 10
the count is 11
the count is 12
the count is 13
the count is 14
the count is 15
加上-race
查看程序运行时候有竞争关系
此时就出问题了
互斥锁
互斥锁是一种常用的同步机制,用于在多个线程中对共享资源进行访问控制。互斥锁的原理是:当一个线程访问共享资源时,其他线程不能访问该共享资源,直到该线程访问完毕。互斥锁可以保证共享资源在同一时刻只被一个线程访问,从而避免了资源的竞争和不一致性。 互斥锁的使用方法如下:
- 定义一个互斥锁变量
- 在需要加锁的地方加锁
- 在需要解锁的地方解锁
var mutex sync.Mutex // 定义一个互斥锁变量
mutex.Lock() // 加锁
//这里面进行操作
mutex.Unlock() // 解锁
💡简单的一个案例如下:
/*
* @Description:资源竞争死锁
* @Author: xiongxinwei 3293172751nss@gmail.com
* @Date: 2022-10-04 21:37:41
* @LastEditTime: 2022-10-25 18:26:07
* @FilePath: \code\go-super\45-main.go
* @Github_Address: https://github.com/cubxxw/awesome-cs-cloudnative-blockchain
* Copyright (c) 2022 by xiongxinwei 3293172751nss@gmail.com, All Rights Reserved. @blog: http://nsddd.top
*/
package main
import (
"fmt"
"sync"
"time"
)
var mutex sync.Mutex // 定义一个互斥锁变量
var wg sync.WaitGroup
var count int
func test() {
mutex.Lock() // 加锁
count++
fmt.Println("the count is ", count)
time.Sleep(100 * time.Millisecond)
mutex.Unlock() // 解锁
wg.Done()
}
func test2() {
mutex.Lock() // 加锁
count++
fmt.Println("the count is ", count)
time.Sleep(100 * time.Millisecond)
mutex.Unlock() // 解锁
wg.Done()
}
func main() {
for i := 0; i < 100; i++ {
wg.Add(1)
go test()
go test2()
}
wg.Wait()
}
/*
互斥锁是一种常用的同步机制,用于在多个线程中对共享资源进行访问控制。互斥锁的原理是:当一个线程访问共享资源时,其他线程不能访问该共享资源,直到该线程访问完毕。互斥锁可以保证共享资源在同一时刻只被一个线程访问,从而避免了资源的竞争和不一致性。
互斥锁的使用方法如下:
1. 定义一个互斥锁变量
2. 在需要加锁的地方加锁
3. 在需要解锁的地方解锁
var mutex sync.Mutex // 定义一个互斥锁变量
mutex.Lock() // 加锁
这里面进行操作
mutex.Unlock() // 解锁
*/
🚀 编译结果如下:
D:\文档\最近的\awesome-golang\docs\code\go-super>45-main.exe
the count is 1
the count is 2
the count is 3
the count is 4
the count is 5
the count is 6
the count is 7
the count is 8
the count is 9
the count is 10
the count is 11
the count is 12
the count is 13
the count is 14
the count is 15
the count is 16
the count is 17
the count is 18
the count is 19
the count is 20
the count is 21
the count is 22
the count is 23
the count is 24
the count is 25
the count is 26
the count is 27
the count is 28
the count is 29
the count is 30
the count is 31
the count is 32
the count is 33
the count is 34
the count is 35
the count is 36
the count is 37
the count is 38
the count is 39
the count is 40
the count is 41
the count is 42
the count is 43
the count is 44
the count is 45
the count is 46
the count is 47
the count is 48
the count is 49
the count is 50
the count is 51
the count is 52
the count is 53
the count is 54
the count is 55
the count is 56
the count is 57
the count is 58
the count is 59
the count is 60
the count is 61
the count is 62
the count is 63
the count is 64
the count is 65
the count is 66
the count is 67
the count is 68
the count is 69
the count is 70
the count is 71
the count is 72
the count is 73
the count is 74
the count is 75
the count is 76
the count is 77
the count is 78
the count is 79
the count is 80
the count is 81
the count is 82
the count is 83
the count is 84
the count is 85
the count is 86
the count is 87
the count is 88
the count is 89
the count is 90
the count is 91
the count is 92
the count is 93
the count is 94
the count is 95
the count is 96
the count is 97
the count is 98
the count is 99
the count is 100
the count is 101
the count is 102
总结
读写锁可以让多个读操作并发,同时读取,但是对于写操作是完全互斥的,也就是说,当一个goroutine进行写操作的时候,其他的goroutine既不能进行读操作,也不能进行写操作。
END 链接
✴️版权声明 © :本书所有内容遵循CC-BY-SA 3.0协议(署名-相同方式共享)©