用喜欢和舒服的方式在Golang中使用锁、使用channel自定义锁

pathbox 发表了文章 • 4 个评论 • 214 次浏览 • 6 天前 • 来自相关话题

众所周知,我们能使用Golang轻松编写并发程序。Golang利用goroutine,让我们编写并发程序变得容易。并发程序中重要的问题之一就是如何正确的处理“竞争资源”或“共享资源”。Golang为我们提供了锁的机制。这篇文章,就简单介绍Golang中锁... 查看全部

众所周知,我们能使用Golang轻松编写并发程序。Golang利用goroutine,让我们编写并发程序变得容易。并发程序中重要的问题之一就是如何正确的处理“竞争资源”或“共享资源”。Golang为我们提供了锁的机制。这篇文章,就简单介绍Golang中锁的使用方法。并且进行错误的使用方法和正确的使用方法的代码示例对比。文章的所以代码示例在:https://github.com/pathbox/learning-go/tree/master/src/lock


原文链接


我们看第一个栗子:


package main

import (
"fmt"
"sync"
)

type Counter struct {
Value int
}

var wg sync.WaitGroup
var mutex sync.Mutex // 声明了一个全局锁
func main() {

wg.Add(1000)
counter := &Counter{Value: 0}

for i := 0; i < 1000; i++ {
go Count(counter, mutex)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}

func Count(counter *Counter, mutex sync.Mutex) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}

/*
输出结果:
Count Value: 982
*/

这里声明了一个全局锁 sync.Mutex,然后将这个全局锁以参数的方式代入到方法中,这样并没有真正起到加锁的作用。


正确的方式是:


package main

import (
"fmt"
"sync"
)

type Counter struct {
Value int
}

var wg sync.WaitGroup
var mutex sync.Mutex // 声明了一个全局锁
func main() {

wg.Add(1000)
counter := &Counter{Value: 0}

for i := 0; i < 1000; i++ {
go Count(counter)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}

func Count(counter *Counter) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}

/*
输出结果:
Count Value: 1000
*/

声明了一个全局锁后,其作用范围是全局。直接使用,而不是将其作为参数传递到方法中。


下一个栗子


package main

import (
"fmt"
"sync"
)

type Counter struct {
Value int
}

var wg sync.WaitGroup

func main() {
var mutex sync.Mutex // 声明了一个非全局锁
wg.Add(1000)
counter := &Counter{Value: 0}

for i := 0; i < 1000; i++ {
go Count(counter, mutex)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}

func Count(counter *Counter, mutex sync.Mutex) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}

/*
输出结果:
Count Value: 954
*/

上面栗子中,声明的不是全局锁。然后将这个锁作为参数传入到Count()方法中,这样并没有真正起到加锁的作用。


正确的方式:


package main

import (
"fmt"
"sync"
)

type Counter struct {
Value int
}

var wg sync.WaitGroup

func main() {
mutex := &sync.Mutex{} // 定义了一个锁 mutex,赋值给mutex
wg.Add(1000)
counter := &Counter{Value: 0}

for i := 0; i < 1000; i++ {
go Count(counter, mutex)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}

func Count(counter *Counter, mutex *sync.Mutex) {
mutex.Lock()
defer mutex.Unlock()
counter.Value++
wg.Done()
}

/*
输出结果:
Count Value: 1000
*/

这次通过 mutex := &sync.Mutex{},定义了mutex,然后作为参数传递到方法中,正确实现了加锁功能。


简单的说,在全局声明全局锁,之后这个全局锁就能在代码中的作用域范围内都能使用了。但是,也许你需要的不是全局锁。这和锁的粒度有关。
所以,你可以声明一个锁,在其作用域范围内使用,并且这个作用域范围是有并发执行的,别将锁当成参数传递。如果,需要将锁当成参数传递,那么你传的不是一个锁的声明,而是这个锁的指针。


下面,我们讨论一种更好的使用方式。通过阅读过很多”牛人“写的Go的程序或源码库,在锁的使用中。常常将锁放入对应的 struct 中定义,我觉得这是一种不错的方法。


package main

import (
"fmt"
"sync"
)

type Counter struct {
Value int
sync.Mutex
}

var wg sync.WaitGroup

func main() {

wg.Add(1000)
counter := &Counter{Value: 0}

for i := 0; i < 1000; i++ {
go Count(counter)
}
wg.Wait()
fmt.Println("Count Value: ", counter.Value)
}

func Count(counter *Counter) {
counter.Lock()
defer counter.Unlock()
counter.Value++
wg.Done()
}

/*
输出结果:
Count Value: 1000
*/

这样,我们声明的不是全局锁,并且这个需要加锁的竞争资源也正是 struct Counter 本身的Value属性,反映了这个锁的粒度。我觉得这是一种很舒服的使用方式(暂不知道这种方式会带来什么负面影响,如果有踩过坑的朋友,欢迎聊一聊这个坑),当然,如果你需要全局锁,那么请定义全局锁。


还可以有更多的使用方式:


// 1.
type Counter struct {
Value int
Mutex sync.Mutex
}

counter := &Counter{Value: 0}
counter.Mutex.Lock()
defer counter.Mutex.Unlock()

//2.
type Counter struct {
Value int
Mutex *sync.Mutex
}

counter := &Counter{Value: 0, Mutex: &sync.Mutex{}}
counter.Mutex.Lock()
defer counter.Mutex.Unlock()

Choose the way you like~


接下来,我们自己尝试创建一个互斥锁。


简单的说,简单的互斥锁锁的原理是:一个线程(进程)拿到了这个互斥锁,在这个时刻,只有这个线程(进程)能够进行互斥锁锁的范围中的"共享资源"的操作,主要是写操作。我们这里不讨论读锁的实现。锁的种类很多,有不同的实现场景和功能。这里我们讨论的是最简单的互斥锁。


我们能够利用Golang 的channel所具有特性,创建一个简单的互斥锁。


/locker/locker.go


package locker

// Mutext struct
type Mutex struct {
lock chan struct{}
}

// 创建一个互斥锁
func NewMutex() *Mutex {
return &Mutex{lock: make(chan struct{}, 1)}
}

// 锁操作
func (m *Mutex) Lock() {
m.lock <- struct{}{}
}

// 解锁操作
func (m *Mutex) Unlock() {
<-m.lock
}

main.go


package main

import (
"./locker"
"fmt"
"time"
)

type record struct {
lock *locker.Mutex
lock_count int
no_lock_count int
}

func newRecord() *record {
return &record{
lock: locker.NewMutex(),
lock_count: 0,
no_lock_count: 0,
}
}

func main() {
r := newRecord()

for i := 0; i < 1000; i++ {
go CountWithoutLock(r)
go CountWithLock(r)
}
time.Sleep(2 * time.Second)
fmt.Println("Record no_lock_count: ", r.no_lock_count)
fmt.Println("Record lock_count: ", r.lock_count)
}

func CountWithLock(r *record) {
r.lock.Lock()
defer r.lock.Unlock()
r.lock_count++
}

func CountWithoutLock(r *record) {
r.no_lock_count++
}

/* 输出结果
Record no_lock_count: 995
Record lock_count: 1000
*/

locker 就是通过使用channel的读操作和写操作会互相阻塞等待的这个同步性质。
可以简单的理解为,channel中传递的就是互斥锁。一个线程(进程)申请了一个互斥锁(struct{}{}),将这个互斥锁存放在channel中,
其他线程(进程)就没法申请互斥锁放入channel,而处于阻塞状态,等待channel恢复空闲空间。该线程(进程)进行操作”共享资源“,然后释放这个互斥锁(从channel中取走),channel这时候恢复了空闲的空间,其他线程(进程)
就能申请互斥锁并且放入channel。这样,在某一时刻,只会有一个线程(进程)拥有互斥锁,在操作"共享资源"。

用Go实例学习Protobuf编码

ochapman 发表了文章 • 1 个评论 • 180 次浏览 • 6 天前 • 来自相关话题

Protobuf使用非常广泛,但是内部是怎么编码的呢,本文用Go的案例来学习protobuf编码规则。 https://slide.mua.io...

阳历和阴历相互转化的工具类 golang版本

nosix 发表了文章 • 1 个评论 • 120 次浏览 • 3 天前 • 来自相关话题

github 地址 https://github.com/nosixtools/solarlunar

  1. 实现了阳历和阴历... 查看全部

github 地址 https://github.com/nosixtools/solarlunar



  1. 实现了阳历和阴历的相互转化,支持1900年到2049年。

  2. 支持节假日的计算


分享给你们


转化例子


package main 

import (
"github.com/nosixtools/solarlunar"
"fmt"
)

func main() {
solarDate := "1990-05-06"
fmt.Println(solarlunar.SolarToChineseLuanr(solarDate))
fmt.Println(solarlunar.SolarToSimpleLuanr(solarDate))

lunarDate := "1990-04-12"
fmt.Println(solarlunar.LunarToSolar(lunarDate, false))
}

节假日计算例子


package main

import (
"fmt"
"github.com/nosixtools/solarlunar/festival"
)

func main() {
festival := festival.NewFestival("./festival.json")
fmt.Println(festival.GetFestivals("2017-08-28"))
fmt.Println(festival.GetFestivals("2017-05-01"))
fmt.Println(festival.GetFestivals("2017-04-05"))
fmt.Println(festival.GetFestivals("2017-10-01"))
fmt.Println(festival.GetFestivals("2018-02-15"))
fmt.Println(festival.GetFestivals("2018-02-16"))
}