channel(管道)以及互斥锁

[toc]

😶‍🌫️go语言官方编程指南:https://golang.org/#open in new window

go语言的官方文档学习笔记很全,推荐去官网学习

😶‍🌫️我的学习笔记:github: https://github.com/3293172751/golang-rearnopen in new window


区块链技术(也称之为分布式账本技术),是一种互联网数据库技术,其特点是去中心化,公开透明,让每一个人均可参与的数据库记录

❤️💕💕关于区块链技术,可以关注我,共同学习更多的区块链技术。博客http://nsddd.topopen in new window


不同的协程如何通信?

方法:

  1. 全局变量加锁同步
  2. 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/3293172751/cs-awesome-Block_Chain
 * 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)
}

💡 上面的方法不需要关闭管道。

截图案例

image-20221025150553321

image-20221025150456599

image-20221025150403386

image-20221025150523179

image-20221025151321673

image-20221025151331434

image-20221025150718790

管道是一个应用类型,使用函数变化的是地址

向管道写入数据

/*************************************************************************
    > 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底层分析

⚡ 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的固定值。

情况:

  1. 遍历时,channel没有关闭,出现deadlock的错误
  2. 遍历时,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/3293172751/cs-awesome-Block_Chain
 * 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/3293172751/cs-awesome-Block_Chain
 * 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/3293172751/cs-awesome-Block_Chain
 * 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/3293172751/cs-awesome-Block_Chain
 * 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/3293172751/cs-awesome-Block_Chain
 * 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查看程序运行时候有竞争关系

此时就出问题了

互斥锁

互斥锁是一种常用的同步机制,用于在多个线程中对共享资源进行访问控制。互斥锁的原理是:当一个线程访问共享资源时,其他线程不能访问该共享资源,直到该线程访问完毕。互斥锁可以保证共享资源在同一时刻只被一个线程访问,从而避免了资源的竞争和不一致性。 互斥锁的使用方法如下:

  1. 定义一个互斥锁变量
  2. 在需要加锁的地方加锁
  3. 在需要解锁的地方解锁
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/3293172751/cs-awesome-Block_Chain
 * 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 链接