美文网首页
GO语言并发和锁

GO语言并发和锁

作者: 夜凉听风雨 | 来源:发表于2020-04-11 15:26 被阅读0次

使用goroutine来写一个开启2个协程执行同一个方法的代码。声明一个add函数, 在函数内循环五万次,每次让全局变量x加1。在main函数中,开辟两个goroutine调用add方法。

package main
import (
    "fmt"
    "sync"
)

var x int
var wg sync.WaitGroup

func add() {
    for i := 0; i < 50000; i++ {
        x = x + 1
    }
    wg.Done()
}


func main() {
    wg.Add(2)
    go add()
    go add()
    wg.Wait() // 等待两个协程执行完毕
    fmt.Println(x)
}

预期: 按照正常思维两个add函数同时运行,每个函数让x加运算五万次,最后两个goroutine运行完毕,结果应该是打印x为10万。

结果: 打印结果远远不到10万

原因: 多个协程同时争抢同一个资源。

分析: 比如当x=100的时候,此时第一个协程正在执行x = x + 1的,x的值为100,而第二个协程也正在执行x = x + 1这一步,这个时候就是两个协程在同时争抢同一个资源x。它们执行完毕,x都为101,相当于少加了一次。所以我们最后得到的x结果远远小于10万。

如何解决这个问题呢?

下面就需要用锁来处理

  • 互斥锁

package main
import (
    "fmt"
    "sync"
)

var x int
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
    for i := 0; i < 50000; i++ {
        lock.Lock() // 上锁
        x = x + 1
        lock.Unlock() // 解锁
    }
    wg.Done()
}


func main() {
    
    wg.Add(2)
    go add()
    go add()
    wg.Wait()
    fmt.Println(x)
}

使用sync.Mutex就可以为代码加上互斥锁。在上述代码中,使用lock.Lock()为x = x + 1这句代码上锁。当一个协程正在执行x = x + 1时,其他协程在执行这句代码之前必须等待,只有当上一个正在执行这句代码的协程执行结束,解锁后其他协程才能有一个协程执行x = x + 1

运行后打印结果为100000,符合预期。

  • 读写互斥锁

互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。
读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。

总结一下:
1.同时只能有一个goroutine 获取写锁
2.同时可以有多个goroutine 获取读锁
3.同时只能有多个读锁或者只有一个写锁
写锁的时候,其他协程啥也不干,当读锁的时候其他协程也可以去读锁但不能去写锁

package main
import (
    "fmt"
    "sync"
    "time"
)

var (
    x int
    y int
    wg sync.WaitGroup
    lock sync.Mutex
)

func write() {
    wg.Add(1)
    lock.Lock()
    x = x + 1
    time.Sleep(time.Millisecond * 1)
    lock.Unlock()
    wg.Done()
}


func read() {
    wg.Add(1)
    lock.Lock()
    y = x
    time.Sleep(time.Millisecond * 1)
    lock.Unlock()
    wg.Done()
}

func main() {
    
    start := time.Now()
    
    for i := 0; i < 1000; i++ {
        go write()
    }

    for i := 0; i < 10000; i++ {
        go read()
    }
    
    wg.Wait()
    end := time.Now()
    fmt.Println(end.Sub(start))
    fmt.Println(x,y)
}

互斥锁打印结果.png
package main
import (
    "fmt"
    "sync"
    "time"
)

var (
    x int
    y int
    wg sync.WaitGroup
    lock sync.RWMutex
)

func write() {
    wg.Add(1)
    lock.Lock()
    x = x + 1
    time.Sleep(time.Millisecond * 1)
    lock.Unlock()
    wg.Done()
}


func read() {
    wg.Add(1)
    lock.RLock()
    y = x
    time.Sleep(time.Millisecond * 1)
    lock.RUnlock()
    wg.Done()
}

func main() {
    
    start := time.Now()
    
    for i := 0; i < 1000; i++ {
        go write()
    }

    for i := 0; i < 10000; i++ {
        go read()
    }
    
    wg.Wait()
    end := time.Now()
    fmt.Println(end.Sub(start))
    fmt.Println(x,y)
}

读写互斥锁打印结果.png

对比上面两份代码和打印结果,可以发现:互斥锁耗时更长,读写互斥锁耗时更短。同时执行读操作的协程很早就完成了,同时执行写操作的协程则更晚才完成。因为读操作的协程可以同时执行不需要相互等待,而写操作的协程必须等待上一个写操作的协程或者上一个读操作的协程执行完毕才能执行。

相关文章

  • GO语言并发和锁

    使用goroutine来写一个开启2个协程执行同一个方法的代码。声明一个add函数, 在函数内循环五万次,每次让全...

  • Go语言并发

    Go语言并发 Go语言级别支持协程,叫做goroutine Go 语言从语言层面支持并发和并行的开发操作 Go并发...

  • Go语言高并发Map解决方案

    Go语言高并发Map解决方案 Go语言基础库中的map不是并发安全的,不过基于读写锁可以实现线程安全;不过在Go1...

  • Go控制程序生命周期

    近期学了go语言的并发编程,掌握了 并发原语-原子锁,互斥锁,通道 并发原理 并发设计模式今天我们就来锻炼一下,如...

  • Go并发

    并发和并行 Go是并发语言,而不是并行语言。(Go is a concurrent language and no...

  • golang 基础(31) 锁

    在 go 语言中提供了并发编程模型和工具外,还提供传统的同步工具锁,包括互斥锁和读写锁有关互斥锁有些内容我们必须清...

  • Go基础语法(九)

    Go语言并发 Go 是并发式语言,而不是并行式语言。 并发是指立即处理多个任务的能力。 Go 编程语言原生支持并发...

  • Go sync.Mutex

    Go语言提供了sync包和channel机制来解决并发机制中不同goroutine之间的同步和通信 锁 Locke...

  • Go 并发原理

    Go语言是为并发而生的语言,Go语言是为数不多的在语言层面实现并发的语言;也正是Go语言的并发特性,吸引了全球无数...

  • Go语言并发、锁、channel

    多线程同时执行叫做并行 并发就是在不同线程中来回切换执行来达到并行的效果就是并发 通过go可以在当前线程中开启一个...

网友评论

      本文标题:GO语言并发和锁

      本文链接:https://www.haomeiwen.com/subject/higymhtx.html