为了账号安全,请及时绑定邮箱和手机立即绑定

一段时间后停止 gouroutine

一段时间后停止 gouroutine

Go
有只小跳蛙 2022-04-26 15:56:39
像这样在一段时间后停止 gouroutine 是否并发安全?代码:(注意:由于另一个 goroutine 的变化导致数据竞争):okpackage mainimport (    "fmt"    "time")func main() {    var ok byte    time.AfterFunc(1000*time.Millisecond, func() {        ok = 1    })    var i uint64    for ok == 0 {        i++ // CPU intensive task    }    fmt.Println(i) // 2_776_813_033}终端:go run -race .==================WARNING: DATA RACEWrite at 0x00c000132010 by goroutine 8:  main.main.func1()      ./main.go:11 +0x46Previous read at 0x00c000132010 by main goroutine:  main.main()      ./main.go:15 +0xf4Goroutine 8 (running) created at:  time.goFunc()      go/src/time/sleep.go:180 +0x51==================80849692Found 1 data race(s)代码(无数据竞争):package mainimport (    "fmt"    "sync/atomic"    "time")func main() {    var ok int32    time.AfterFunc(1000*time.Millisecond, func() {        atomic.StoreInt32(&ok, 1)    })    var i uint64    for atomic.LoadInt32(&ok) == 0 {        i++ // CPU intensive task    }    fmt.Println(i) // 2_835_935_488}终端:go run -race .31934042
查看完整描述

2 回答

?
墨色风雨

TA贡献1853条经验 获得超6个赞

即使被另一个 goroutineok设置为,也不能保证忙等待循环将终止。false在设置和读取 的过程中没有显式同步ok,因此不能保证主 goroutine 看到对其所做的更改。换句话说,没有办法在两个 goroutine 之间建立发生前的关系。

https://golang.org/ref/mem

代码的第二个版本是安全的,即使在 Go 内存模型中ok没有关于 . 原子读/写具有发生之前关系所必需的内存屏障。您应该使用同步原语之一(互斥体、通道)来保证这一点。


查看完整回答
反对 回复 2022-04-26
?
月关宝盒

TA贡献1772条经验 获得超5个赞

Go 内存模型:


建议

修改多个 goroutine 同时访问的数据的程序必须序列化这种访问。

要序列化访问,请使用通道操作或其他同步原语(例如同步和同步/原子包中的同步原语)保护数据。


对于第一个代码,您应该使用适当的同步,例如:

“上下文”、“同步/原子” sync.Mutex、或通道。


去1.14


Goroutines 现在是异步可抢占的。因此,没有函数调用的循环不再可能使调度程序死锁或显着延迟垃圾收集。这在除 windows/arm、darwin/arm、js/wasm 和 plan9/* 之外的所有平台上都受支持。


一段时间后停止 gouroutine

BenchmarkAfterFunc-8            1000000000 0.4468 ns/op  0 B/op  0 allocs/op

BenchmarkDoneChannel-8          121966824   9.855 ns/op  0 B/op  0 allocs/op

BenchmarkTimeSince-8            89790115    12.95 ns/op  0 B/op  0 allocs/op

BenchmarkContextErr-8           58508900    19.78 ns/op  0 B/op  0 allocs/op

BenchmarkAfterFuncMutex-8       58323207    20.00 ns/op  0 B/op  0 allocs/op

BenchmarkContext-8              48947625    27.43 ns/op  0 B/op  0 allocs/op

测试:


package main


import (

    "context"

    "sync"

    "sync/atomic"

    "testing"

    "time"

)


const d = 200 * time.Millisecond //  To stop a task after a period of time


func BenchmarkTimeSince(b *testing.B) {

    t0 := time.Now()

    var count = 0

    for i := 0; i < b.N; i++ {

        if time.Since(t0) < d {

            count++

        }

    }

    _ = count

}


func BenchmarkContext(b *testing.B) {

    var ctx, cancel = context.WithTimeout(context.Background(), d)

    defer cancel()

    var count = 0

    for i := 0; i < b.N; i++ {

        select {

        case <-ctx.Done():

            // break

        default:

            count++

        }

    }

    _ = count

}

func BenchmarkContextErr(b *testing.B) {

    var ctx, cancel = context.WithTimeout(context.Background(), d)

    defer cancel()

    var count = 0

    for i := 0; i < b.N; i++ {

        if ctx.Err() == nil {

            count++

        }

    }

    _ = count

}


func BenchmarkAfterFunc(b *testing.B) {

    var done uint32

    time.AfterFunc(d, func() { atomic.StoreUint32(&done, 1) })

    var count = 0

    for i := 0; i < b.N; i++ {

        if atomic.LoadUint32(&done) == 0 {

            count++

        }

    }

    _ = count

}


func BenchmarkDoneChannel(b *testing.B) {

    var done = make(chan struct{})

    time.AfterFunc(d, func() { close(done) })

    var count = 0

    for i := 0; i < b.N; i++ {

        select {

        case <-done:

            // break

        default:

            count++

        }

    }

    _ = count

}


type foo struct {

    sync.Mutex

    state bool

}


func (p *foo) end() {

    p.Lock()

    p.state = true

    p.Unlock()

}

func (p *foo) isDone() bool {

    var b bool

    p.Lock()

    b = p.state

    p.Unlock()

    return b

}

func BenchmarkAfterFuncMutex(b *testing.B) {

    var it = foo{}

    time.AfterFunc(d, func() { it.end() })

    var count = 0

    for i := 0; i < b.N; i++ {

        if it.isDone() {

            count++

        }

    }

    _ = count

}


https://medium.com/a-journey-with-go/go-asynchronous-preemption-b5194227371c


抢占是调度器的重要组成部分,它可以在 goroutine 之间分配运行时间。事实上,如果没有抢占,一个长时间运行的占用 CPU 的 goroutine 会阻止其他 goroutine 被调度。1.14 版本引入了一种异步抢占的新技术,为调度程序提供了更多的权力和控制权。


查看完整回答
反对 回复 2022-04-26
  • 2 回答
  • 0 关注
  • 158 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信