数据争用和竞态条件

操作系统 刘宇帅 3年前 阅读量: 534

在多线程编程中数据争用和竞态条件经常会被提到,怎奈何我写了4年PHP而PHP不支持线程并发编程,虽然这两个概念说的具体问题我还是有所了解,但是初看到这两个概念其实听陌生的。

数据争用

多个线程对同一个变量、同时地、进行读/写操作并且至少有一个线程进行写操作的现象叫做数据争用。如果发生了数据争用,那么一个多线程程序运行完毕时受到数据争用的变量的值是不可预测的。我们可以用锁来避免数据争用。
数据争用的例子(go)

package main

import (
    "fmt"
    "sync"
)

var (
    N         = 0
    waitgroup sync.WaitGroup
)

func counter(number *int, i int) {
    //fmt.Println(i)
    *number++
    waitgroup.Done()
}

func main() {
    for i := 0; i < 1000; i++ {
        waitgroup.Add(1)
        go counter(&N, i)
    }
    waitgroup.Wait()
    fmt.Println(N)
}

这段程序很简单我们启用1000个 goroutine 对N进行+1操作。我们可以运行这段代码,结果是无法预测的,这就是因为发生了数据争用,我们起的1000个 goroutine 同时对N进行写操作,这是符合数据争用的条件的,所以结果是无法预测的。
而我们上面也说了我们可以用锁来解决数据争用问题:

package main

import (
    "fmt"
    "sync"
)

var (
    N         = 0
    lock      = sync.Mutex{}    // 新增
    waitgroup sync.WaitGroup
)

func counter(number *int, i int) {
    lock.Lock()    // 新增
    *number++
    lock.Unlock()    // 新增
    waitgroup.Done()
}

func main() {

    for i := 0; i < 1000; i++ {
        waitgroup.Add(1)
        go counter(&N, i)
    }
    waitgroup.Wait()
    fmt.Println(N)
}

如上代码所示,我们再次运行,运行结果一直是1000。我们在原来代码中添加三行,就是利用 sync.Mutex 在对数据写操作的时候加上锁即可解决。

竞态条件

受多个线程上代码执行顺序和时机的影响,程序的运行结果会发生变化的现象称作竞态条件。如果存在竞态条件那么程序对于同一个输入多次运行结果将无法预测。其实竞态条件就跟数据库中的事务一样,在一个事务执行过程中有另外一个事务也执行操作同样的数据导致数据不一致的问题,解决办法就是让“事务”具有"原子性"。 竞态竞争的例子(go)

type singleton struct {
}

var (
    instance *singleton
)

func GetInstance() *singleton {
    if instance == nil {
        instance = &singleton{}
    }

    return instance
}

这里写一个简单的单例模式,我们在 GetInstance 里做了if判断然后在去 new 一个新的实例。如果在多线程模式下,某一个线程做了 if 判断,当 new 新实例的时候这时候 instance 未必还是 nil,因为其他线程可能在这期间正好做了实例化操作。 解决竞态条件也是使用锁,对整个”事务“使用锁就可以解决了,上面的问题解决方法:

type singleton struct {
}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })

    return instance
}

提示

功能待开通!


暂无评论~