Golang同步:锁的使用案例详解

互斥锁

互斥锁是传统的并发程序对共享资源进行访问控制的主要手段。它由标准库代码包sync中的Mutex结构体类型代表。只有两个公开方法

Lock

Unlock

类型sync.Mutex的零值表示了未被锁定的互斥量。

var mutex sync.Mutex

mutex.Lock()

1

2

1

2

示例

// test for Go

//

// Copyright (c) 2015 - Batu <1235355@qq.com>

package main

import (

"fmt"

"sync"

"time"

)

func main(){

//声明

var mutex sync.Mutex

fmt.Println("Lock the lock. (G0)")

//加锁mutex

mutex.Lock()

fmt.Println("The lock is locked.(G0)")

for i := 1; i < 4; i++ {

go func(i int) {

fmt.Printf("Lock the lock. (G%d)\n", i)

mutex.Lock()

fmt.Printf("The lock is locked. (G%d)\n", i)

}(i)

}

//休息一会,等待打印结果

time.Sleep(time.Second)

fmt.Println("Unlock the lock. (G0)")

//解锁mutex

mutex.Unlock()

fmt.Println("The lock is unlocked. (G0)")

//休息一会,等待打印结果

time.Sleep(time.Second)

}

打印结果:

/usr/local/go/bin/go run /Users/liuxinming/go/src/example/test/test.go

Lock the lock. (G0)

The lock is locked.(G0)

Lock the lock. (G1)

Lock the lock. (G2)

Lock the lock. (G3)

Unlock the lock. (G0)

The lock is unlocked. (G0)

The lock is locked. (G1)

建议:同一个互斥锁的成对锁定和解锁操作放在同一层次的代码块中。

读写锁

针对读写操作的互斥锁,它可以分别针对读操作和写操作进行锁定和解锁操作。读写锁遵循的访问控制规则与互斥锁有所不同。

它允许任意读操作同时进行

同一时刻,只允许有一个写操作进行

==============华丽分割线============

并且一个写操作被进行过程中,读操作的进行也是不被允许的

读写锁控制下的多个写操作之间都是互斥的

写操作与读操作之间也都是互斥的

多个读操作之间却不存在互斥关系

读写锁由结构体类型sync.RWMutex代表

写操作的锁定和解锁

* func (*RWMutex) Lock

* func (*RWMutex) Unlock

读操作的锁定和解锁

* func (*RWMutex) Rlock

* func (*RWMutex) RUnlock

注意:

+ 写解锁在进行的时候会试图唤醒所有因欲进行读锁定而被阻塞的Goroutine.

+ 读解锁在进行的时候只会在已无任何读锁定的情况下试图唤醒一个因欲进行写锁定而被阻塞的Goroutine

+ 若对一个未被写锁定的读写锁进行写解锁,会引起一个运行时的恐慌

+ 而对一个未被读锁定的读写锁进行读解锁却不会如此

锁的完整示例

// test for Go

//

// Copyright (c) 2015 - Batu <1235355@qq.com>

//

// 创建一个文件存放数据,在同一时刻,可能会有多个Goroutine分别进行对此文件的写操作和读操作.

// 每一次写操作都应该向这个文件写入若干个字节的数据,作为一个独立的数据块存在,这意味着写操作之间不能彼此干扰,写入的内容之间也不能出现穿插和混淆的情况

// 每一次读操作都应该从这个文件中读取一个独立完整的数据块.它们读取的数据块不能重复,且需要按顺序读取.

// 例如: 第一个读操作读取了数据块1,第二个操作就应该读取数据块2,第三个读操作则应该读取数据块3,以此类推

// 对于这些读操作是否可以被同时执行,不做要求. 即使同时进行,也应该保持先后顺序.

package main

import (

"fmt"

"sync"

"time"

"os"

"errors"

"io"

)

//数据文件的接口类型

type DataFile interface {

// 读取一个数据块

Read() (rsn int64, d Data, err error)

// 写入一个数据块

Write(d Data) (wsn int64, err error)

// 获取最后读取的数据块的序列号

Rsn() int64

// 获取最后写入的数据块的序列号

Wsn() int64

// 获取数据块的长度

DataLen() uint32

}

//数据类型

type Data []byte

//数据文件的实现类型

type myDataFile struct {

f *os.File //文件

fmutex sync.RWMutex //被用于文件的读写锁

woffset int64 // 写操作需要用到的偏移量

roffset int64 // 读操作需要用到的偏移量

wmutex sync.Mutex // 写操作需要用到的互斥锁

rmutex sync.Mutex // 读操作需要用到的互斥锁

dataLen uint32 //数据块长度

}

//初始化DataFile类型值的函数,返回一个DataFile类型的值

func NewDataFile(path string, dataLen uint32) (DataFile, error){

f, err := os.OpenFile(path, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0666)

//f,err := os.Create(path)

if err != nil {

fmt.Println("Fail to find", f, "cServer start Failed")

return nil, err

}

if dataLen == 0 {

return nil, errors.New("Invalid data length!")

}

df := &myDataFile{

f : f,

dataLen:dataLen,

}

return df, nil

}

//获取并更新读偏移量,根据读偏移量从文件中读取一块数据,把该数据块封装成一个Data类型值并将其作为结果值返回

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 {

//由于进行写操作的Goroutine比进行读操作的Goroutine少,所以过不了多久读偏移量roffset的值就会大于写偏移量woffset的值

// 也就是说,读操作很快就没有数据块可读了,这种情况会让df.f.ReadAt方法返回的第二个结果值为代表的非nil且会与io.EOF相等的值

// 因此不应该把EOF看成错误的边界情况

// so 在读操作读完数据块,EOF时解锁读操作,并继续循环,尝试获取同一个数据块,直到获取成功为止.

if err == io.EOF {

//注意,如果在该for代码块被执行期间,一直让读写所fmutex处于读锁定状态,那么针对它的写操作将永远不会成功.

//切相应的Goroutine也会被一直阻塞.因为它们是互斥的.

// so 在每条return & continue 语句的前面加入一个针对该读写锁的读解锁操作

df.fmutex.RUnlock()

//注意,出现EOF时可能是很多意外情况,如文件被删除,文件损坏等

//这里可以考虑把逻辑提交给上层处理.

continue

}

}

break

}

d = bytes

df.fmutex.RUnlock()

return

}

func (df *myDataFile) Write(d Data) (wsn int64, err error){

//读取并更新写的偏移量

var offset int64

df.wmutex.Lock()

offset = df.woffset

df.woffset += int64(df.dataLen)

df.wmutex.Unlock()

//写入一个数据块,最后写入数据块的序号

wsn = offset / int64(df.dataLen)

var bytes []byte

if len(d) > int(df.dataLen){

bytes = d[0:df.dataLen]

}else{

bytes = d

}

df.fmutex.Lock()

df.fmutex.Unlock()

_, err = df.f.Write(bytes)

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) DataLen() uint32 {

return df.dataLen

}

func main(){

//简单测试下结果

var dataFile DataFile

dataFile,_ = NewDataFile("./mutex_2015_1.dat", 10)

var d=map[int]Data{

1:[]byte("batu_test1"),

2:[]byte("batu_test2"),

3:[]byte("test1_batu"),

}

//写入数据

for i:= 1; i < 4; i++ {

go func(i int){

wsn,_ := dataFile.Write(d[i])

fmt.Println("write i=", i,",wsn=",wsn, ",success.")

}(i)

}

//读取数据

for i:= 1; i < 4; i++ {

go func(i int){

rsn,d,_ := dataFile.Read()

fmt.Println("Read i=", i,",rsn=",rsn,",data=",d, ",success.")

}(i)

}

time.Sleep(10 * time.Second)

}

打印结果:

/usr/local/go/bin/go run /Users/liuxinming/go/src/example/test/test.go

write i= 3 ,wsn= 1 ,success.

write i= 1 ,wsn= 0 ,success.

Read i= 1 ,rsn= 1 ,data= [116 101 115 116 49 95 98 97 116 117] ,success.

write i= 2 ,wsn= 2 ,success.

Read i= 2 ,rsn= 0 ,data= [98 97 116 117 95 116 101 115 116 49] ,success.

Read i= 3 ,rsn= 2 ,data= [98 97 116 117 95 116 101 115 116 50] ,success.

本文分享自微信公众号 - Golang语言社区(Golangweb)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-04-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

Golang语言社区--LollipopGO开源项目搭建商城路由分发

大家好,我是Golang社区主编彬哥,还是要继续社区的开源项目LollipopGO轻量级web框架实战商城。

790200
来自专栏Golang语言社区

[转载]Go JSON 技巧

相对于很多的语言来说, Go 的 JSON 解析可谓简单至极. 问题 通常情况下, 我们在 Go 中经常这样进行 JSON 的解码: package main ...

41430
来自专栏Golang语言社区

Golang Template 简明笔记

作者:人世间 链接:https://www.jianshu.com/p/05671bab2357 來源:简书 前后端分离的Restful架构大行其道,传统的模板...

99360
来自专栏Golang语言社区

[转载]Golang 编译成 DLL 文件

首先撰写 golang 程序 exportgo.go: package main import "C" import "fmt" //export Print...

43440
来自专栏Golang语言社区

[基础篇]Go语言变量

变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。变量可以通过变量名访问。 Go 语言变量名由字母、数字、下划线组成,其中首个字母不能为数字。 声...

41770
来自专栏Golang语言社区

Go 语言构建高并发分布式系统实践

你知道互联网最抢手的技术人才有哪些吗?最新互联网职场生态报告显示,最抢手的十大互联网技术人才排名中Go语言开发人员位居第三,从中不难见得,Go语言的渗透率越来越...

78750
来自专栏深度学习之tensorflow实战篇

golang 格式“占位符”%d,%f,%s等应用类型

golang 的fmt 包实现了格式化I/O函数,类似于C的 printf 和 scanf。 红色部分为常用占位符 ? ? ? ? ? ? ? 对于 ...

40870
来自专栏Golang语言社区

51. Socket服务端和客户端使用TCP协议通讯 | 厚土Go学习笔记

Socket服务器是网络服务中常用的服务器。使用 go 语言实现这个业务场景是很容易的。 这样的网络通讯,需要一个服务端和至少一个客户端。 我们计划构建一个这样...

30440
来自专栏Golang语言社区

厚土Go学习笔记 | 29. 接口

在go语言中,接口类型是由一组方法定义的集合。 一个类型是否实现了一个接口,就看这个类型是否实现了接口中定义的所有方法。在go语言中,无需特别的指明定义一个接口...

38550
来自专栏Golang语言社区

Go代码打通HTTPs

TL;DR 手工创建CA证书链,手写代码打通HTTPs的两端 HTTPs最近是一个重要的话题,同时也是一个有点难懂的话题。所以网上有大量的HTTPs/TLS/S...

45140

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励