猿问

GOMAXPROCS 为 1 时是否需要锁

GOMAXPROCS变量限制了可以同时执行用户级 Go 代码的操作系统线程数。

因此,如果GOMAXPROCS为 1,无论我有多少个 goroutine,map从不同的 goroutine访问变量(如)都是安全的,无需任何锁。正确的?


拉丁的传说
浏览 456回答 3
3回答

叮当猫咪

简短的回答是,“不”它不安全。长答案真的太长了,无法在这里详细解释,但我会给出一个简短的摘要和一些文章链接,这些链接应该可以帮助您将各个部分放在一起。让我们首先区分“并发”和“并行”。考虑两个函数。并行运行它们都可以在不同的处理器上同时执行。同时运行其中一个或两个,或者两者都可能正在执行但两者都能够执行。如果它们是并发的但不是并行的,那么它们正在切换——并且没有通道或锁,我们不能保证先到哪里的顺序。考虑“并发但不并行”可能很奇怪,但考虑到相反的情况是相当不起眼的,并行但不并发;我的文本编辑器、终端和浏览器都是并行运行的,但绝对不是并发的。因此,如果两个(或 20,000 个)函数可以访问相同的内存,比如一个写入和一个读取,并且它们同时运行,那么可能首先发生写入,也可能首先发生读取。除非我们负责调度/排序,因此没有任何保证,因此锁和通道。将 GOMAXSPROCS 设置为大于 1 可以使并发程序并行运行,但可能不会,所有并发goroutine 可能位于一个 CPU 线程上,也可能位于多个 CPU 线程上。因此,将 GOMAXPROCS 设置为 1 并不能保证并发进程在没有锁或通道来协调其执行的情况下是安全的。线程[通常]由操作系统调度。请参阅Wikipedia或您最喜欢的人类知识库。Goroutines 由 Go 调度。接下来考虑这个:即使使用 [a] 单个逻辑处理器和操作系统线程,也可以调度数十万个 goroutine 以惊人的效率和性能同时运行。还有这个:在我们的应用程序中构建并发的问题最终是我们的 goroutine 将尝试访问相同的资源,可能是同时。对共享资源的读写操作必须始终是原子的。换句话说,读取和写入必须一次由一个 goroutine 发生,否则我们会在程序中创建竞争条件。从 这篇文章,它很好地解释了差异并引用了您可能想要查找的其他一些材料(这篇文章有些过时,因为 GMAXPROCS 不再默认为 1,但一般理论仍然是准确的)。最后,Effective Go 刚开始时可能会让人望而生畏,但它是必读的。这是Go中并发的解释。

Qyouu

是的,即使您在一个处理器上运行程序,仍然需要锁。并发和并行是不同的东西,你会在这里找到一个很好的解释。这里只是一个小例子:func main() {&nbsp; &nbsp; runtime.GOMAXPROCS(1)&nbsp; &nbsp; t := &test{}&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for i := 0; i < 100; i++ {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Some computation prior to using t.Num&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(300 * time.Microsecond)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; num := t.Num&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Some computation using num&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(300 * time.Microsecond)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.Num = num + 1&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; for i := 0; i < 100; i++ {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; num := t.Num&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Some computation using num&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(300 * time.Microsecond)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.Num = num + 1&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; time.Sleep(1 * time.Second) // Wait goroutines to finish&nbsp; &nbsp; fmt.Println(t.Num)}睡眠时间代表一些需要一些时间的计算。我想保持示例的可运行性和简单性,这就是我使用它的原因。运行它时,即使在单个处理器上,输出也不是我们想要的 200。所以是的,当同时访问变量时,锁是必要的,否则你会遇到问题。

扬帆大鱼

改变状态,runtime.GOMAXPROCS(1)假设,将失败;即使只有两个 go-routines:func main() {&nbsp; &nbsp; runtime.GOMAXPROCS(1)&nbsp; &nbsp; start := make(chan struct{})&nbsp; &nbsp; wg := &sync.WaitGroup{}&nbsp; &nbsp; N := 2 //10, 1000, 10000, ... fails with even 2 go-routines&nbsp; &nbsp; for i := 0; i < N; i++ {&nbsp; &nbsp; &nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; &nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <-start&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //processing state&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; initialState := globalState&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //give another goroutine a chance, by halting this one&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //and lend some processing cycles&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //(also simulating "concurrent" processing of initialState)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; runtime.Gosched()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if globalState != initialState {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; panic(fmt.Sprintf("oops! %d != %d", initialState, globalState))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; globalState = initialState + 1&nbsp; &nbsp; &nbsp; &nbsp; }()&nbsp; &nbsp; }&nbsp; &nbsp; close(start)&nbsp; &nbsp; wg.Wait()&nbsp; &nbsp; log.Println(`global state:`, globalState)}var (&nbsp; &nbsp; globalState int)其他答案更详细 - 有利于研究并发编程的不同方面。
随时随地看视频慕课网APP

相关分类

Go
我要回答