Go基础之锁的初识
当我们的程序就一个线程的时候是不需要用到锁的,但是通常我们实际的代码不会是单个线程的,所有这个时候就需要用到锁了,那么关于锁的使用场景主要涉及到哪些呢?
- 当我们多个线程在读相同的数据的时候则是需要加锁的
- 当我们的程序既有读又有写的时候更是需要加锁的
- 当我们有多个线程在写的时候同样也是需要加锁
互斥锁
互斥锁:同一个时刻只有一个线程能够拿到锁
我们先通过一个例子来演示,如果当多个线程同时更改一个变量,结果会是怎么样
不加锁版本
package main import (
"sync"
"fmt"
) var (
//lock sync.Mutex
count int
w sync.WaitGroup //用于等待子线程执行完之后退出
) func main() {
w.Add(1) // 在调用线程前执行w.add
go func(){
for i:=0;i<100000;i++{
count++
}
w.Done() //执行完 执行w.Done
}()
for i :=0;i<100000;i++{
count++
}
w.Wait() // 最后执行w.wait等待所有的线程执行完毕
fmt.Println(count) }
当我们运行多次就可以发现,最后的结果基本不可能是我们先看到的:200000
我们修改代码代码需要加锁保护的地方加上锁,并且这里加的是互斥锁,修改后的代码为:
package main import (
"sync"
"fmt"
) var (
lock sync.Mutex
count int
w sync.WaitGroup //用于等待子线程执行完之后退出
) func main() {
w.Add(1) // 在调用线程前执行w.add
go func(){
for i:=0;i<100000;i++{
lock.Lock()
count++
lock.Unlock()
}
w.Done() //执行完 执行w.Done
}()
for i :=0;i<100000;i++{
lock.Lock()
count++
lock.Unlock()
}
w.Wait() // 最后执行w.wait等待所有的线程执行完毕
fmt.Println(count) }
这次当我们多次运行的时候,就能保证我们每次都能看到我们想要的值:200000
接下来看读写锁
读写锁
读写锁主要用到读多写少的场景
读写锁分为:读锁和写锁
如果自己设置了一个写锁,那么其他读的线程以及写的线程都拿不到锁,这个时候和互斥锁的功能相同
如果自己设置了一个读锁,那么其他写的线程是拿不到锁的,但是其他读的线程都是可以拿到这个锁
我们把上面的例子代码进行更改:
package main import (
"sync"
"fmt"
) var (
rwlock sync.RWMutex
w sync.WaitGroup
count int
) func main() {
w.Add(1)
go func(){
for i:=0;i<1000000;i++{
rwlock.Lock() // 这里定义了一个写锁
count++
rwlock.Unlock()
}
w.Done()
}() for i:=0;i<1000000;i++{
rwlock.Lock() // 这里定义了一个写锁
count++
rwlock.Unlock()
}
w.Wait()
fmt.Println(count)
}
通过设置写锁,我们同样可以实现数据的一致性
下面是一个读锁的使用例子:
package main import (
"sync"
"fmt"
) var (
rwlock sync.RWMutex
w sync.WaitGroup
count int
) func main() {
w.Add(1)
go func(){
for i:=0;i<1000000;i++{
rwlock.Lock() // 这里定义了一个写锁
count++
rwlock.Unlock()
}
w.Done()
}() for i:=0;i<16;i++{
w.Add(1)
go func(){
rwlock.RLock() //这里定义了一个读锁
fmt.Println(count)
rwlock.RUnlock() //释放读锁
w.Done()
}()
}
w.Wait()
fmt.Println(count)
}
Go中的原子操作
原子操作,我们则不需加锁,也能保证数据的一致性
并且如果只是计算,那么原子操作则是最快的
实例代码:
package main import (
"sync"
//"time"
"sync/atomic"
"fmt"
) var (
w sync.WaitGroup
count int32
) func main() {
w.Add(1)
//start := time.Now().UnixNano()
go func() {
for i:=0;i<1000000;i++{
atomic.AddInt32(&count,1)
}
w.Done()
}() for i:=0;i<1000000;i++{
atomic.AddInt32(&count,1)
}
w.Wait()
//end := time.Now().UnixNano()
//fmt.Println((end- start)/1000/1000)
fmt.Println(count)
}
关于互斥锁的补充
互斥锁需要注意的问题:
1、不要重复锁定互斥锁
2、不要忘记解锁互斥锁, 必要时使用defer语句
3、不要对尚未锁定或者已解锁的互斥锁解锁
4、不要对在多个函数之间直接传递互斥锁
对已经锁定的互斥锁进行锁定,会立即阻塞当前的goroutine 这个goroutine所执行的流程会一直停滞在该调用互斥锁的Lock方法的那行代码
所谓死锁: 当前程序中的主goroutine以及我们启用的那些goroutine 都已经被阻塞,这些goroutine可以被称为用户级的goroutine 这就相当于整个程序已经停滞不前了,并且这个时候go程序会抛出如下的panic:
fatal error: all goroutines are asleep - deadlock!
并且go语言运行时系统抛出自行抛出的panic都属于致命性错误,都是无法被恢复的,调用recover函数对他们起不到任何作用
Go语言中的互斥锁是开箱即用的,也就是我们声明一个sync.Mutex 类型的变量,就可以直接使用它了,需要注意:该类型是一个结构体类型,属于值类型的一种,把它传给一个函数
将它从函数中返回,把它赋值给其他变量,让它进入某个通道都会导致他的副本的产生。并且原值和副本以及多个副本之间是完全独立的,他们都是不同的互斥锁
所以不应该将锁通过函数的参数进行传递
关于读写锁的补充
1、在写锁已被锁定的情况下再次试图锁定写锁,会阻塞当前的goroutine
2、在写锁已被锁定的情况下再次试图锁定读锁,也会阻塞当前的goroutine
3、在读锁已被锁定的情况下试图锁定写锁,同样会阻塞当前的goroutine
4、在读锁已被锁定的情况下再试图锁定读锁,并不会阻塞当前的goroutine
对于某个受到读写锁保护的共享资源,多个写操作不能同时进行,写操作和读操作也不能同时进行,但多个读操作却可以同时进行
对写锁进行解锁,会唤醒“所有因试图锁定读锁,而被阻塞的goroutine”, 并且这个通常会使他们都成功完成对读锁的锁定
对读锁进行解锁,只会在没有其他读锁锁定的前提下,唤醒“因试图锁定写锁,而被阻塞的goroutine” 并且只会有一个被唤醒的goroutine能够成功完成对写锁的锁定,其他的goroutine
还要在原处继续等待,至于哪一个goroutine,那么就要看谁等待的事件最长
解锁读写锁中未被锁定的写锁, 会立即引发panic ,对其中的读锁也是如此,并且同样是不可恢复的
Go基础之锁的初识的更多相关文章
- [转]Go基础之锁的初识
当我们的程序就一个线程的时候是不需要用到锁的,但是通常我们实际的代码不会是单个线程的,所有这个时候就需要用到锁了,那么关于锁的使用场景主要涉及到哪些呢? 当我们多个线程在读相同的数据的时候则是需要加锁 ...
- 006 01 Android 零基础入门 01 Java基础语法 01 Java初识 06 使用Eclipse开发Java程序
006 01 Android 零基础入门 01 Java基础语法 01 Java初识 06 使用Eclipse开发Java程序 Eclipse下创建程序 创建程序分为以下几个步骤: 1.首先是创建一个 ...
- 005 01 Android 零基础入门 01 Java基础语法 01 Java初识 05 Eclipse简介
005 01 Android 零基础入门 01 Java基础语法 01 Java初识 05 Eclipse简介 Eclipse是一款集成开发工具--IDE. 集成开发环境(IDE,Integrated ...
- 004 01 Android 零基础入门 01 Java基础语法 01 Java初识 04 Java程序的结构
004 01 Android 零基础入门 01 Java基础语法 01 Java初识 04 Java程序的结构 Java程序的结构 Java程序外层--类 程序外层,如下面的代码,是一个类的定义. c ...
- 003 01 Android 零基础入门 01 Java基础语法 01 Java初识 03 Java程序的执行流程
003 01 Android 零基础入门 01 Java基础语法 01 Java初识 03 Java程序的执行流程 Java程序长啥样? 首先编写一个Java程序 记事本编写程序 打开记事本 1.wi ...
- 002 01 Android 零基础入门 01 Java基础语法 01 Java初识 02 Java简介
002 01 Android 零基础入门 01 Java基础语法 01 Java初识 02 Java简介 学习Java的基础语法 Java是一门编程语言,学习的逻辑其实和现实世界的语言是一样的,需要了 ...
- 001 01 Android 零基础入门 01 Java基础语法 01 Java初识 01 导学
001 01 Android 零基础入门 01 Java基础语法 01 Java初识 01 导学 welcome to Java World 欢迎来到Java世界 一起领略Java编程世界的奥秘与奥妙 ...
- python学习笔记(基础四:模块初识、pyc和PyCodeObject是什么)
一.模块初识(一) 模块,也叫库.库有标准库第三方库. 注意事项:文件名不能和导入的模块名相同 1. sys模块 import sys print(sys.path) #打印环境变量 print(sy ...
- [转]《Hadoop基础教程》之初识Hadoop
原文地址:http://blessht.iteye.com/blog/2095675 Hadoop一直是我想学习的技术,正巧最近项目组要做电子商城,我就开始研究Hadoop,虽然最后鉴定Hadoop不 ...
随机推荐
- nginx笔记4-负载均衡带来的问题以及解决办法
接着笔记3,将笔记三的改造一下,现在分别启动两个Tomcat,在页面获取session.如图所示: tomcat2的session: tomcat1的session: 根据上图发现,每个tomcat取 ...
- ubuntu常用命令操作
建立文件夹软链接 ln -s 源文件 目标文件 当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在其它的目录下 ...
- vim编辑器——常用操作整理
注意:以下的操作都是在命令状态下进行的,不要进入插入状态了.参考这里 1.删除 dd 删除一行 ndd 删除以当前行开始的n行dw 删除以当前字符开始的一个字符ndw 删除 ...
- swift 学习之自动引用计数
swift 学习之自动引用计数 学习和研究的主要是"实例对象和实例对象直接的相会强引用所产生的内从泄漏"和"使用闭包产生的强引用造成的内存泄漏" 注意:只有以引 ...
- <CEPH中国-深圳站-技术交流会演讲PPT> YY云平台Ceph Block应用实践 & 我写的书 《CEPH实战》
YY云平台Ceph Block应用实践 http://s3.yyclouds.com/public/YY%E4%BA%91%E5%B9%B3%E5%8F%B0Ceph%E5%AE%9E%E8%B7%B ...
- 用DirectDraw封装的位图动画类
头文件 [cpp] view plaincopyprint? #pragma once #include <vector> using namespace std; #include &l ...
- Netty的并发编程实践1:正确使用锁
很多刚接触多线程编程的开发者,虽然意识到了并发访问可变变量需要加锁,但是对于锁的范围.加锁的时机和锁的协同缺乏认识,往往会导致出现一些问题.下面笔者就结合Netty的代码来讲解下这方面的知识. 打开F ...
- Linux显示一个二进制文件或可执行文件的完整路径
Linux显示一个二进制文件或可执行文件的完整路径 youhaidong@youhaidong-ThinkPad-Edge-E545:~$ which halt /sbin/halt
- TortoiseSVN设置忽略文件和目录文件夹
TortoiseSVN设置忽略文件和目录文件夹 在多数项目中你总会有文件和目录不需要进行版本控制.这可能包括一些由编译器生成的文件,*.obj,*.lst,或许是一个用于存放可执行程序的输出文件夹. ...
- 弹出层罩子html(上传照片弹出请等待后面的代码不能修改)
一,效果 二,素材 三,代码 <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> ...