go语言之并发编程同步一
前面介绍了采用go语法的并行操作以及channel。既然是并行操作,那么就涉及到数据原子性以及同步的问题。所以在Go里面也需要采用同步的机制。
互斥锁:
由标准库代码包sync中的Mutex结构体类型表示。Sync.Mutex类型只有两个公开的指针方法-Lock和Unlock。声明方法如下:
var mutex sync.Mutex
既然有加锁,那也有解锁。但是如果在代码中忘了解锁就会导致比如流程异常,线程执行停滞,甚至程序死锁等问题。在Go中,这个错误可以通过defer语句来降低发生的概率。比如下面的方法, defer语句保证了在该函数执行结束之前互斥锁mutex一定会被解锁。
var mutex sync.Mutex
func write(){
mutex.Lock()
defer mutex.Unlock()
}
来看下具体的例子:
func main(){
var mutex sync.Mutex
fmt.Println("Lock the lock.(main)")
mutex.Lock()
fmt.Println("The lock is locked.(main)")
for i:=1;i<=3;i++{
go func(i int){
fmt.Println("Lock the lock.g",i,"\n")
mutex.Lock()
fmt.Println("The lock is locked.g",i,"\n")
}(i)
}
time.Sleep(time.Second)
fmt.Println("unlock the lock.(main)")
mutex.Unlock()
fmt.Println("the lock is unlocked.(main)")
time.Sleep(time.Second)
}
在函数中启用了3个goroutine,并分别命名为g1,g2,g3.在启用这3个goroutine之前就已经对互斥锁mutex进行了锁定,并且在那3个go函数的开始处加入了对mutex的锁定操作。当for语句执行完毕后,先让main睡眠1秒钟,以便运行时系统有充足的时间开始运行g1,g2,g3。在这之后,我们解锁mutex。
运行结果:从结果中可以看到, main函数进行锁定后。g1,g2,g3都无法进行锁定操作且被阻塞,直到main函数解锁之后,这个时候被阻塞的g1,g2和g3都有机会重新锁定该互斥锁。但只有一个goroutine会锁定成功。而其他的goroutine将继续阻塞,直到有新的机会到来。
Lock the lock.(main)
The lock is locked.(main)
Lock the lock.g 1
Lock the lock.g 2
Lock the lock.g 3
unlock the lock.(main)
the lock is unlocked.(main)
The lock is locked.g 1
如果我们在go函数里面结束的时候都加上mutex.Unlock()语句。那么所有的三个goroutine都会进行锁定
Lock the lock.(main)
The lock is locked.(main)
Lock the lock.g 3
Lock the lock.g 1
Lock the lock.g 2
unlock the lock.(main)
the lock is unlocked.(main)
The lock is locked.g 3
The lock is locked.g 1
The lock is locked.g 2
读写锁:
读写锁即针对读写操作的互斥锁。与普通的互斥锁最大的不同就是可以分别针对读操作和写操作进行锁定和解锁操作。读写锁遵循的访问控制规则和互斥锁有所不同。读写锁控制下的多个写操作之间都是互斥的,并且写操作和读操作之间也都是互斥的。但是多个读操作之间却不存在互斥关系。在这样的互斥策略下,读写锁可以在大大降低因使用锁而造成的性能损耗的情况下,完成对共享资源的访问控制。
Go中的读写锁由结构体类型sync.RWMutex表示,与互斥锁一样,sync.RWMutex类型的零值就已经是可用的读写锁实例了。
func (*RWMutex) Lock()
func (*RWMutex) Unlock()
以及
func (*RWMutex) RLock()
func (*RWMutex) RUnLock()
前一对方法代表了对写操作的锁定和解锁。后一对方法代表了对读操作的锁定和解锁。
写解锁会试图唤醒所有因欲进行读锁定的而被阻塞的goroutine。而读解锁只会在已无任何读锁定的情况下,试图唤醒一个因欲进行写锁定而不阻塞的goroutine。如果对一个未被写锁定的读写锁进行写解锁,那么就会造成一个不可恢复的恐慌。对一个未被读锁定的读写锁进行读解锁也是一样的情况。
func main(){
var rwm sync.RWMutex
for i:=0;i<3;i++{
go func(i int){
fmt.Println("try to lock for reading....",i,"\n")
rwm.RLock()
fmt.Println("locked for reading,",i,"\n")
time.Sleep(time.Second*2)
fmt.Println("try to unlock for reading....",i,"\n")
rwm.RUnlock()
fmt.Println("unlocked for reading....",i,"\n")
}(i)
}
time.Sleep(time.Millisecond*100)
fmt.Println("try to locked for writting")
rwm.Lock()
fmt.Println("Locked for writting")
}
在这个例子中,启用了3个goroutine用于读写锁rwm的读锁定和读解锁操作。其中读解锁操作会有延迟2s进行模拟真实的情况。先让住goroutine睡眠100ms。以使那3个go函数有足够的时间执行。之后对rwm的写锁定操作必定会让住goroutine阻塞。因为此时go函数中的读锁定已经进行且还未进行读解锁操作。经过2秒之后,当go函数中的读解锁操作都已完成时,main函数中的写锁定操作才会成功完成。运行结果如下:
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 locked for writting
try to unlock for reading.... 0
unlocked for reading.... 0
try to unlock for reading.... 2
try to unlock for reading.... 1
unlocked for reading.... 1
Locked for writing
来看一个具体操作文件的例子:
type DataFile interface{
Read()(rsn int64,d Data,err error)
Write(d Data)(wsn int64,err error)
RSN() int64
WSN() int64
DataLen() uint32
Close() error
}
首先创建一个数据文件的接口类型。里面包含了读,写操作,写入数据块的序列号和最后读取块的序列号,数据长度以及关闭。
然后来编写DataFile接口的实现类型。myDataFile共有7个字段。fmutex控制对文件的读写锁操作,woffset和roffset分别对应读,写偏置。wmutex和rmutex控制对woffset和roffset的锁操作
type myDataFile struct{
f *os.File
fmutex sync.RWMutex
woffset int64
roffset int64
wmutex sync.Mutex
rmutex sync.Mutex
dataLen uint32
}
新建一个文件的实例,把变量df作为返回值的前提是要myDataFile实现DataFile接口的所有实现方法。因此还需要为*myDataFile编写DataFile的所有方法。
func NewDataFile(path string,dataLen uint32) (DataFile,error){
f,err:=os.Create(path)
if err != nil{
return nil,err
}
if dataLen == 0{
return nil,errors.New("invalid data length")
}
df:=&myDataFile{f:f,dataLen:dataLen}
return df,nil
}
在对offset进行操作前,需要用互斥锁锁住避免多个进程进行操作。在读取文件的数据块的时候进行读锁定。但是这个代码有个问题就是当多个goroutine并行执行的时候,读偏置roffset有可能会大于写偏置woffset,这会导致没有数据可读。ReadAt返回的第二个结果就是io.EOF. 导致调用发读取错误的数据
func (df *myDataFile) Read() (rsn int64,d Data,err error){
var offset int64
df.rmutex.Lock()
offset=df.roffset
df.roffset+=int64(df.dataLen)
df.rmutex.Unlock()
rsn=offset/int64(df.dataLen)
df.fmutex.Lock()
defer df.fmutex.Unlock()
bytes:=make([]bytes,df.dataLen)
_,err=df.f.ReadAt(bytes,offset)
if err !=nil {
return
}
d=bytes
return
}
因此对读操作的边界情况加入保护。代码如下。当遇到io.EOF的时候,会尝试读取同样的数据块。在for循环开始的时候也会一直保持读锁定。针对这个文件的写操作goroutine都会被阻塞。所以在continue以及return之前都必须加上df.fmutex.RUnlock()进行读解锁。
func (df *myDataFile) Read() (rsn int64,d Data,err error){
var offset int64
df.rmutex.Lock()
offset=df.roffset
df.roffset+=int64(df.dataLen)
df.rmutex.Unlock()
rsn=offset/int64(df.dataLen)
bytes:=make([]byte,df.dataLen)
for{
df.fmutex.RLock()
_,err=df.f.ReadAt(bytes,offset)
if err !=nil{
if err == io.EOF{
df.fmutex.Unlock()
continue
}
df.fmutex.RUnlock()
return
}
d=bytes
df.fmutex.RUnlock()
return
}
}
func (df *myDataFile) Write(d Data) (wsn int64,err error){
var offset int64
var bytes []byte
df.wmutex.Lock()
offset=df.woffset
df.woffset+=int64(df.dataLen)
df.wmutex.Unlock()
wsn=offset/int64(df.dataLen)
if len(d) > int(df.dataLen) {
bytes = d[0:df.dataLen]
}else{
bytes=d
}
df.fmutex.Lock()
defer df.fmutex.Unlock()
return
}
func(df *myDataFile) RSN() int64{
df.rmutex.Lock()
defer df.rmutex.Unlock()
return df.roffset/int64(df.dataLen)
}
func(df *myDataFile) WSN() int64{
df.wmutex.Lock()
defer df.wmutex.Unlock()
return df.woffset/int64(df.dataLen)
}
func(df *myDataFile) Close() error{
error:=df.f.Close()
return error
}
func(df *myDataFile) DataLen() uint32{
return df.dataLen
}
go语言之并发编程同步一的更多相关文章
- Go语言 7 并发编程
文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/ Go学习群:415660935 今天我们学习Go语言编程的第七章,并发编程.语言级别 ...
- Go语言之并发编程(二)
通道(channel) 单纯地将函数并发执行是没有意义的.函数与函数间需要交换数据才能体现并发执行函数的意义.虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题 ...
- Go语言之并发编程(四)
同步 Go 程序可以使用通道进行多个 goroutine 间的数据交换,但这仅仅是数据同步中的一种方法.通道内部的实现依然使用了各种锁,因此优雅代码的代价是性能.在某些轻量级的场合,原子访问(atom ...
- Go语言之并发编程(三)
Telnet回音服务器 Telnet协议是TCP/IP协议族中的一种.它允许用户(Telnet客户端)通过一个协商过程与一个远程设备进行通信.本例将使用一部分Telnet协议与服务器进行通信. 服务器 ...
- Go语言之并发编程(一)
轻量级线程(goroutine) 在编写socket网络程序时,需要提前准备一个线程池为每一个socket的收发包分配一个线程.开发人员需要在线程数量和CPU数量间建立一个对应关系,以保证每个任务能及 ...
- go语言之并发编程 channel(1)
单向channel: 单向通道可分为发送通道和接收通道.但是无论哪一种单向通道,都不应该出现在变量的声明中,假如初始化了这样一个变量 var uselessChan chan <- int =m ...
- go语言之并发编程 channel
前面介绍了goroutine的用法,如果有多个goroutine的话相互之间是如何传递数据和通信的呢.在C语言或者JAVA中,传输的方法包括共享内存,管道,信号.而在Go语言中,有了更方便的方法,就是 ...
- python 并发编程 同步调用和异步调用 回调函数
提交任务的两张方式: 1.同步调用 2.异步调用 同步调用:提交完任务后,就在原地等待任务执行完后,拿到结果,再执行下一行代码 同步调用,导致程序串行执行 from concurrent.future ...
- Java并发编程--同步容器
BlockingQueue 阻塞队列 对于阻塞队列,如果BlockingQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤 ...
随机推荐
- javascript中window与document对象、setInterval与setTimeout定时器的用法与区别
一.写在前面 本人前端菜鸟一枚,学习前端不久,学习过程中有很多概念.定义在使用时容易混淆,在此给向我一样刚踏入前端之门的童鞋们归纳一下.今天给大家分享一下js中window与document对象.se ...
- 小程序图表功能wxchart实现
在开发小程序过程中,有项目用到图表功能, 其实Echart.js有小程序的库,我们要吧引入进来,然后配置初始化一下,就可以达到目的了.接下来就开始步骤吧 首先下载JS库:http://download ...
- C# 委托和Lambda表达式
看了一些资料,简要的总结一下委托,Lambda,事件. 委托. 1)委托的含义 委托定义了函数类型,是一种类似“C++函数指针”的东西. 但委托和函数指针还是不同的,函数指针不过是一个函数的入口地址( ...
- SpringMVC学习(一)小demo
首先看一下整个demo的项目结构: 第一步是导入Spring MVC单独使用时的最少jar包: 第二步在项目的web.xml中配置Spring MVC提供的拦截请求的Servlet: 全类名是:org ...
- Android Studio 使用笔记:给包重命名~~有点水
很简单,选择需要重命名的包,按下 Shift + F6 这时候出现提示,选择 Rename package 输入新的包名,Refactor按钮会变亮,点击就可以了. 注意:这个是重命名一个包名,想做 ...
- Yarn源码分析之MRAppMaster:作业运行方式Local、Uber、Non-Uber
基于作业大小因素,MRAppMaster提供了三种作业运行方式:本地Local模式.Uber模式.Non-Uber模式.其中, 1.本地Local模式:通常用于调试: 2.Uber模式:为降低小作业延 ...
- libubox组件(1)——usock
一:相关API介绍 1.相关源码文件:usocket.h usocket.c 2.类型标志 1: #define USOCK_TCP 0 2: #define USOCK_UDP 1 3: #defi ...
- Windows 下tomcat安装及将多个tomcat注册为Windows服务
一.应用场景 虽然Windows在当下已经不再是我们作为服务器操作系统平台的首选,但是还是有一些开发商或者项目整体需求的限制必须运行在Windows系统平台之下.为了避免多个应用部署在同一个tomca ...
- solr-in-action-ch4-Configuring Solr
Solr基本的三个XML配置文件: solr.xml: solr 日志.shard.solrcould等配置 solrconfig.xml: 某个solr core的配置 schema.xml:某个s ...
- POJ 3480 & HDU 1907 John(尼姆博弈变形)
题目链接: PKU:http://poj.org/problem? id=3480 HDU:http://acm.hdu.edu.cn/showproblem.php? pid=1907 Descri ...