Golang从入门到放弃200711--Lock

  • 参考文章
    Go语言并发之道
    Go并发编程

  • 互斥锁、读写锁
    临界区: 程序中需要独占访问共享资源的区域

    • 互斥锁

      1. 锁住临界区
      2. 由 sync.Mutex结构体类型表示
      3. 只有两个公开的指针方法 : Lock() 和 Unlock()
      4. sync.Mutex类型的零值表示未被锁定的互斥量
      5. 如果锁定了一个已锁定的互斥锁,重复锁定操作的goroutine将被阻塞
      6. 如果给一个未锁定的互斥锁进行解锁操作时,就会panic,不可恢复
      7. 首次使用后,互斥锁不能复制
      8. 锁可以被多个协程共享,但建议同一个互斥锁的加锁解锁在同一层次
    • 读写锁

      1. 锁住临界区
      2. 由 sync.RWMutex结构体类型表示
      3. 有两对方法:
        Lock() 和 Unlock() 写操作的锁定和解锁
        RLock() 和 RUnlock() 读操作的锁定和解锁
      4. sync.Mutex类型的零值表示未被锁定的互斥量
      5. 与互斥锁的不同:分别对读操作和写操作进行锁定和解锁操作
      6. 没有锁定的读写两类锁,进行对应的解锁时,都会被panic,不可恢复
      7. 首次使用后,读写锁不能复制
      8. 写独占,读共享,写锁优先级高
  • Demo示例加深印象

    • 互斥锁

      1. 如果锁定一个已经锁定的互斥锁,重复锁定操作的goroutine将会被阻塞
        var lock sync.Mutex
        fmt.Println("Lock the lock.(main)")
        lock.Lock()
        fmt.Println("The lock is locked.(main)")
        for i := 1; i <= 3; i++ {
        	go func(i int) {
        		fmt.Printf("Lock the lock.(g%d)\n", i)
        		lock.Lock()
        		//defer lock.Unlock()  加不加之后结果比较
        		fmt.Printf("The lock is locked.(g%d)\n", i)
        	}(i)
        }
        time.Sleep(time.Second)
        fmt.Println("Unlock the lock.(main)")
        lock.Unlock()
        fmt.Println("The lock is unlocked.(main)")
        time.Sleep(time.Second)
        

      运行之后的结果:
      Lock the lock.(main) The lock is locked.(main) Lock the lock.(g3) Lock the lock.(g2) Lock the lock.(g1) Unlock the lock.(main) The lock is unlocked.(main) The lock is locked.(g3)
      从结果上可以看出:
      3个goroutine上的都已经提示已经锁住,当未解锁时,只有打印出一个goroutine的结果。如果把注释处的 defer lock.Unlock() 加入代码,就会发现3个
      goroutine都会打印出来。

      1. 如果给一个未锁定的互斥锁进行解锁操作时,就会panic,不可恢复

        	defer func() {
        		fmt.Println("Try to recover the panic.")
        		if  p := recover(); p != nil{
        			fmt.Printf("Recovered the panic(%#v).\n",p)
        		}
        	}()
        	var mutex sync.Mutex
        	mutex.Lock()
        	mutex.Unlock()
        	fmt.Println("Unlock the lock.")
        	mutex.Unlock()
        

        运行结果:

        Unlock the lock again.
        fatal error: sync: unlock of unlocked mutex
        

        可以看到,就算试图去恢复panic,程序依旧终止了

      2. 首次使用后,互斥锁不能复制

        var lock sync.Mutex
        for i := 0; i < 5; i++ {
        	go func() {
        		lock.Lock()
        		j++
        		fmt.Printf("j = %d\t", j)
        		lock.Unlock()
        	}()
        }
        time.Sleep(1 * time.Second)
        

        运行的结果:

        j = 1	j = 2	j = 3	j = 4	j = 5
        

        多次运行之后仍然可以发现,j的结果是顺序打印的。

        更改下代码,让sync.Mutex值传入到goroutine

        var lock sync.Mutex
        for i := 0; i < 5; i++ {
        	go func(lock sync.Mutex) {
        		lock.Lock()
        		j++
        		fmt.Printf("j = %d\t", j)
        		lock.Unlock()
        	}(lock)
        }
        time.Sleep(1 * time.Second)
        

        多次运行之后会发现,值的顺序是不定的。

        这里再埋个坑:无论运行多少次都会发现没有出现抢占的异常

    • 读写锁

      1. 有两对方法:
      2. 写独占,读共享,写锁优先级高
      var rwm sync.RWMutex
      for i := 0; i < 3; i++ {
      	go func(i int) {
      		fmt.Printf("Try to lock for reading...[%d]\n", i)
      		rwm.RLock()
      		fmt.Printf("Locked for reading:[%d]\n", i)
      		time.Sleep(2 * time.Second)
      		fmt.Printf("Try to unlock for reading...[%d]\n", i)
      		rwm.RUnlock()
      		fmt.Printf("Unlocked for reading:[%d]\n", i)
      	}(i)
      }
      time.Sleep(100 * time.Millisecond)  //
      fmt.Println("Try to lock for writing...")
      rwm.Lock()
      fmt.Println("Locked for writing.")
      

      运行结果:

      Try to lock for reading...[0]
      Locked for reading:[0]
      Try to lock for reading...[1]
      Locked for reading:[1]
      Try to lock for reading...[2]
      Locked for reading:[2]
      Try to lock for writing...
      
      
      Try to unlock for reading...[1]
      Unlocked for reading:[1]
      Try to unlock for reading...[2]
      Unlocked for reading:[2]
      Try to unlock for reading...[0]
      Unlocked for reading:[0]
      Locked for writing.
      

      直接看运行结果看不出来什么,我把结果分成了两段,从打印时间上有很明显的间
      隔。很明显因为goroutine中多次读锁锁住后,在main中的 rwm.Lock()阻塞了
      所以,间隔了2s左右后,再次打印出结果goroutine中解锁后的结果。同时写锁成
      功锁住,程序不再阻塞,成功结束

      main中的 time.Sleep(100 * time.Millisecond)挪到main的末尾,多次运
      行,会有两种结果。其中一种结果是:

      Try to lock for writing...
      Locked for writing.
      Try to lock for reading...[0]
      Try to lock for reading...[2]
      Try to lock for reading...[1]
      

      可以发现,当写锁锁定未解锁时,读锁的锁定是会阻塞的。
      写独占,读共享完整的意思是:

      1. 多个写操作之间是互斥的
      2. 写操作与读操作之间也是互斥的
      3. 多个读操作之间不是互斥的
学习 Go(也称为 Golang)语言从入门到精通可以分为以下几个步骤: 1. **安装和环境设置**: - 官方下载 Go 的安装包并配置好 GOPATH(Go 工作路径),用于存放源码、依赖等。 2. **基础语法**: - 学习基本的数据类型(如 int, float, string 等)、变量声明、常量定义。 - 掌握流程控制结构(if-else, for, while, switch)和函数的定义与调用。 3. **模块管理**: - 使用 `go mod` 命令来管理和导入外部库(模块),了解如何编写和使用 `import` 和 `package` 关键字。 4. **并发编程**: - Go 强调并发,理解 Goroutines(轻量级线程)和 Channels(管道通信机制)的概念。 - 学习互斥锁(sync.Mutex)和通道选择器(select)等同步原语。 5. **标准库的探索**: - 熟悉标准库提供的常用功能,如 fmt (格式化)、io (输入/输出)、net (网络)、os (操作系统接口) 等。 6. **HTTP服务器与客户端**: - 学会使用 net/http 包创建简单的 HTTP 服务端和客户端。 7. **Web框架**: - 如果对 Web 开发感兴趣,可以尝试 Gin 或 Beego 这样的轻量级框架。 8. **错误处理与日志记录**: - 学习如何优雅地处理和捕获运行时错误,以及使用 logrus 或 zap 进行日志记录。 9. **项目实战**: - 通过实际项目练习,比如搭建简单的 RESTful API、数据处理工具或游戏后端。 10. **进阶主题**: - 对于高级开发者,可研究 goroutine 性能优化、内存管理(垃圾回收机制)、反射、接口和组合等概念。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值