Golang-临界资源

001#1. 临界资源:指并发环境中多个进程、线程、协程共享的资源;但是在并发编程中对临界资源的处理不当,往往会导致数据不一致的问题

func main() {
    /*
    临界资源
    */
    a := 1
    go func() {
        a = 2
        fmt.Println("goroutine...", a)
    }()


    a = 3
        
    fmt.Println("main goroutine", a)
    
}
因为主的协程执行完之后,关闭了资源,所以子协程没有来的及执行:
main goroutine 3
func main() {
    /*
    临界资源
    */
    a := 1
    go func() {
        a = 2//14行
        fmt.Println("goroutine...", a)
    }()

    a = 3//18行
    time.Sleep(1)
    fmt.Println("main goroutine", a)
    
}
主协程先把赋值为3,在要打印还没打印的时候,睡去之后,子协程持有资源又将其赋值为2
goroutine... 2
main goroutine 2
通过命令行来看一下:
$ go run -race demo10_race.go
==================
//警告:有临界资源
WARNING: DATA RACE
//通过goroutine 6在0x00c00000a0e0处写入:
Write at 0x00c00000a0e0 by goroutine 6:
  main.main.func1()
      E:/GoPath/src/Go_Advanced/demo10_race.go:14 +0x43

//通过主goroutine在0x00c00000a0e0上一次写入:
Previous write at 0x00c00000a0e0 by main goroutine:
  main.main()
      E:/GoPath/src/Go_Advanced/demo10_race.go:18 +0x8f

Goroutine 6 (running) created at:
  main.main()
      E:/GoPath/src/Go_Advanced/demo10_race.go:13 +0x81
==================
main goroutine 2
goroutine... 2
Found 1 data race(s)
exit status 66

2. 并发本身并不复杂,但是因为有了资源竞争的问题,就会有许多莫名其妙的事情

var ticket = 10
func main() {

    go sale("售票口1")
    go sale("售票口2")
    go sale("售票口3")
    go sale("售票口4")

    time.Sleep(5*time.Second)
}

func sale(name string){
    rand.Seed(time.Now().UnixNano())
    for  {
        if ticket > 0  {
            time.Sleep(time.Duration(rand.Intn(1000))*time.Microsecond)
            fmt.Println(name,"售出:", ticket)
            ticket--
        } else {
            fmt.Println("售完票了")
            break
        }
    }

}

售票口4 售出: 10
售票口2 售出: 10
售票口1 售出: 10
售票口3 售出: 10
售票口4 售出: 6
售票口1 售出: 6
售票口2 售出: 5
售票口3 售出: 5
售票口1 售出: 2
售票口2 售出: 1
售完票了
售票口4 售出: 1
售完票了
售票口3 售出: 0
售完票了
售票口1 售出: -2
售完票了

3. 解决:很多其他语言都是同步方式,通过上锁的方式,及某一段时间段,只能允许一个协程来访问这个共享数据, sync同步包;

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {

    /**
    WaitGroup: 同步等待组
        Add(), 设置等待组中要执行的子 goroutine的数量

        Wait(), 让主goroutine处于等待


        Done(),让等待组中的计数器减一,=在子goroutine中结束等待
     */

    wg.Add(2)

    go fun1()
    go fun2()

    fmt.Println("main 进入阻塞状态,等待wg子goroutine结束")

    wg.Wait() //表示goroutine进入等待,意味着阻塞

    fmt.Println("main..结束阻塞")
}

func fun1()  {
    for i:= 1; i<10; i++ {
        fmt.Println("fun1()函数中打印...A", i)
    }

    wg.Done()//执行完会后,将计数器减1 wg.Add(-1)
}
func fun2()  {

    defer wg.Done()
    for j:= 1; j<10; j++ {
        fmt.Println("\tfun2()函数中打印...A", j)
    }
}

4. go并不鼓励用锁来保护临界资源,而是鼓励通过channel将共享状态的变化在各个协程之间传递;要以通信的方式去共享内存;

  • 互斥锁(同步):最简单的,也是最暴力的;降低了性能
var ticket = 10

var mutex sync.Mutex

var wg sync.WaitGroup
func main() {


    wg.Add(4)
    go sale("售票口1")
    go sale("售票口2")
    go sale("售票口3")
    go sale("售票口4")

    wg.Wait()//main要等待
    fmt.Println("程序结束了")
    //time.Sleep(5*time.Second)
}

func sale(name string){
    rand.Seed(time.Now().UnixNano())
    defer wg.Done()
    for  {

        mutex.Lock()
        if ticket > 0  {
            time.Sleep(time.Duration(rand.Intn(1000))*time.Microsecond)
            fmt.Println(name,"售出:", ticket)
            ticket--
        } else {
            mutex.Unlock()
            fmt.Println("售完票了")
            break
        }

        mutex.Unlock()
    }

}
  • 读写锁:针对读或写的互斥锁,锁可以由任意数量的读取器或单个编写器持有;基本遵循两大原则:可以随便读,多个goroutine同时读;写的时候啥也不能干,不能读也不能写。
var rwMutex *sync.RWMutex
var wg *sync.WaitGroup
func main() {
    rwMutex = new(sync.RWMutex)
    wg = new(sync.WaitGroup)

    wg.Add(2)

    go readData(1)
    go readData(2)

    wg.Wait()
    fmt.Println("main over...")

}

func readData(i int){
    defer wg.Done()
    fmt.Println(i, "开始读: read start...")

    rwMutex.RLock()//读操作上锁
    fmt.Println(i, "正在读取数据:reading...")
    time.Sleep(1*time.Second)
    rwMutex.RUnlock()//读操作解锁
    fmt.Println(i, "读结束:read over...")

}
2 开始读: read start...
2 正在读取数据:reading...
1 开始读: read start...
1 正在读取数据:reading...
2 读结束:read over...
1 读结束:read over...
main over...

读操作可以同时进行
var rwMutex *sync.RWMutex
var wg *sync.WaitGroup
func main() {
    rwMutex = new(sync.RWMutex)
    wg = new(sync.WaitGroup)

    wg.Add(3)

    go writeData(1)
    go readData(2)
    go writeData(3)

    wg.Wait()
    fmt.Println("main over...")

}

func writeData(i int)  {
    defer wg.Done()
    fmt.Println(i, "开始写: write start...")

    rwMutex.Lock()//读操作上锁
    fmt.Println(i, "正在写数据:writing...")
    time.Sleep(3*time.Second)
    rwMutex.Unlock()//读操作解锁
    fmt.Println(i, "写结束:write over...")

}
3 开始写: write start...
3 正在写数据:writing...
2 开始读: read start...
1 开始写: write start...
明显卡顿:必须等3写完,解锁之后才可以;这个时候其他两个协程(1,2)是不能操作的
3 写结束:write over...
2 正在读取数据:reading...
2 读结束:read over...
1 正在写数据:writing...
1 写结束:write over...
main over...

标签: none

添加新评论