对beego在并发上的疑惑

有问必答qiangmzsx 回复了问题 • 3 人关注 • 1 个回复 • 698 次浏览 • 2017-08-12 20:50 • 来自相关话题

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

文章分享pathbox 发表了文章 • 4 个评论 • 751 次浏览 • 2017-08-12 18:52 • 来自相关话题

众所周知,我们能使用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。这样,在某一时刻,只会有一个线程(进程)拥有互斥锁,在操作"共享资源"。

云霁科技寻觅优秀gopher

招聘应聘linusning 回复了问题 • 6 人关注 • 4 个回复 • 1810 次浏览 • 2017-08-12 18:12 • 来自相关话题

邀请对在编程中用中文命名有兴趣的一起探讨

回复

有问必答nobodxbodon 发起了问题 • 1 人关注 • 0 个回复 • 606 次浏览 • 2017-08-12 12:50 • 来自相关话题

GOCN每日新闻(2017-08-12)

回复

每日新闻傅小黑 发起了问题 • 1 人关注 • 0 个回复 • 964 次浏览 • 2017-08-12 10:45 • 来自相关话题

go get github.com/elastic/beats 命令出错,求指点

Golangvoidint 回复了问题 • 3 人关注 • 3 个回复 • 758 次浏览 • 2017-08-12 09:10 • 来自相关话题

Go编译的so格式动态链接库能被android调用么?

有问必答Xanthus 回复了问题 • 2 人关注 • 1 个回复 • 683 次浏览 • 2017-08-11 12:21 • 来自相关话题

关于函数跳转问题

有问必答plain 回复了问题 • 3 人关注 • 2 个回复 • 598 次浏览 • 2017-08-11 11:24 • 来自相关话题

求助:关于beego文档获取request body的内容

有问必答llliiinnn 回复了问题 • 3 人关注 • 4 个回复 • 1187 次浏览 • 2017-08-11 10:48 • 来自相关话题

[远程][10k-20k] Team247 招聘全栈工程师

招聘应聘Team247 发表了文章 • 0 个评论 • 635 次浏览 • 2017-08-11 10:17 • 来自相关话题

关于我们

Team247 是一家位于硅谷的软件创业公司。Team247 致力于将智慧产权投资于广大的互联网以及移动应用的初创公司,在过去的一年取得了高速的成长和发展。目前由于业务的不断增加,诚邀全栈工程师的加入

待遇查看全部

关于我们


Team247 是一家位于硅谷的软件创业公司。Team247 致力于将智慧产权投资于广大的互联网以及移动应用的初创公司,在过去的一年取得了高速的成长和发展。目前由于业务的不断增加,诚邀全栈工程师的加入


待遇



  • 社保补助

  • 弹性化工作时间

  • 灵活的工作地点

  • 一年 12 天带薪假


要求



  • 两年以上的全栈开发经验

  • 良好的英语读写能力,可以根据英文文档进行开发

  • 在压力下能快速学习,按照自己的计划高质量地完成工作

  • 具有团队合作精神与沟通协调能力

  • 有安静的工作环境和高速网络连接


联系方式


想用单元测试方法测一个http handler

有问必答bingo1103 回复了问题 • 3 人关注 • 3 个回复 • 648 次浏览 • 2017-08-11 09:46 • 来自相关话题

GOCN每日新闻(2017-08-11)

每日新闻xiaoma 回复了问题 • 2 人关注 • 1 个回复 • 993 次浏览 • 2017-08-11 07:37 • 来自相关话题

开发golang的守护程序,比如网关之类的,需要多进程吗?为什么?

有问必答tupunco 回复了问题 • 3 人关注 • 5 个回复 • 911 次浏览 • 2017-08-10 20:22 • 来自相关话题

vscode可以显示当前方法体所在的方法的名字吗?

有问必答f839903061 回复了问题 • 1 人关注 • 1 个回复 • 638 次浏览 • 2017-08-10 16:51 • 来自相关话题

go语言语法入门之后,应该如何进行之后的学习?

有问必答xkey 回复了问题 • 6 人关注 • 4 个回复 • 1072 次浏览 • 2017-08-10 16:13 • 来自相关话题