Golang中为什么这段代码无法运行?求解答

Golang中为什么这段代码无法运行?求解答 大家好,我叫肖恩,最近刚开始学习Go语言,但在并发代码方面遇到了一个问题:

我有两个函数在运行,但它们共享变量COUNTER,这导致了竞态条件吗?

我尝试过使用Go协程和互斥锁来锁定它,但仍然出现竞态?

你能看看下面的代码,告诉我原因吗:

package main

import (
    "fmt"
    "runtime"
    "sync"
)

var counter int
var wg sync.WaitGroup
var mutex = sync.Mutex{}
var mutex2 = sync.Mutex{}

func main() {
    fmt.Println("CPU count Start: \t", runtime.NumCPU())
    fmt.Println("GoRoutines count Start: \t", runtime.NumGoroutine())

    wg.Add(2)

    go boo()
    go bar()

    wg.Wait()

    fmt.Println("GoRoutines count End: \t", runtime.NumGoroutine())
    fmt.Println("the end : :)")

}

func boo() int {
    i := 0
    for i = 0; i < 10; i++ {
        mutex.Lock()
        counter++
        fmt.Println("count of boo: \t", counter)
        mutex.Unlock()
    }
    wg.Done()
    return counter
}

func bar() int {
    i := 0
    for i = 0; i < 10; i++ {
        mutex2.Lock()
        counter += 2
        fmt.Println("count of bar: \t", counter)
        mutex2.Unlock()
    }
    wg.Done()
    return counter
}

我发现,如果我用time.sleep延迟其中一个函数,它就能运行,但这样不就不是并发代码了吗?

func bar() int {
    time.Sleep(time.Nanosecond * 1)
    i := 0
    for i = 0; i < 10; i++ {
        mutex2.Lock()
        counter += 2
        fmt.Println("count of bar: \t", counter)
        mutex2.Unlock()
    }
    wg.Done()
    return counter
}

迫不及待想听听大家对上述问题的看法,非常感谢任何帮助。我才刚开始编码三周,发现要完全理解它有点困难。

非常感谢。


更多关于Golang中为什么这段代码无法运行?求解答的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

你好Mikoj,

感谢你的回复,我已经尝试使用一个互斥锁,但它仍然存在竞态条件。我想请问一下,为什么你使用了mutex.Lock()并在互斥锁内部没有任何内容的情况下使用了defer

正如我之前在帖子中提到的,你可以延迟其中一个函数,这样它就能工作,但这真的是并行代码吗?

非常感谢你帮助我理解。

Sea。

更多关于Golang中为什么这段代码无法运行?求解答的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Sean_Murphy:

你好 Mikoj,

感谢你的回复,我尝试使用一个互斥锁,但它仍然存在竞态条件。我想请问一下,为什么你使用了 mutex.Lock()defer,但互斥锁内部没有任何代码?

嗯,里面并不是什么都没有,我有返回代码在里面。 首先你需要了解 defer 是如何工作的: https://blog.golang.org/defer-panic-and-recover https://medium.com/a-journey-with-go/go-how-does-defer-statement-work-1a9492689b6e

Sean_Murphy:

正如我之前在帖子中提到的,你可以延迟其中一个函数,这样确实能工作。但这真的是并行代码吗?

非常感谢你帮助我理解。

Sea.

看看这个视频: https://www.youtube.com/watch?v=cN_DpYBzKso

使用一个互斥锁

package main

import (
	"fmt"
	"runtime"
	"sync"
)

var counter int
var wg sync.WaitGroup
var mutex = sync.Mutex{}

func main() {
	fmt.Println("CPU count Start: \t", runtime.NumCPU())
	fmt.Println("GoRoutines count Start: \t", runtime.NumGoroutine())

	wg.Add(2)

	go boo()
	go bar()

	wg.Wait()

	fmt.Println("GoRoutines count End: \t", runtime.NumGoroutine())
	fmt.Println("the end : :)")

}

func boo() int {
	defer wg.Done()
	i := 0
	for i = 0; i < 10; i++ {
		mutex.Lock()
		counter++
		fmt.Println("count of boo: \t", counter)
		mutex.Unlock()
	}
	mutex.Lock()
	defer mutex.Unlock()
	return counter
}

func bar() int {
	defer wg.Done()
	i := 0
	for i = 0; i < 10; i++ {
		mutex.Lock()
		counter += 2
		fmt.Println("count of bar: \t", counter)
		mutex.Unlock()
	}
	mutex.Lock()
	defer mutex.Unlock()
	return counter
}

你的代码存在两个关键问题:

  1. 使用了两个不同的互斥锁mutexmutex2 分别保护 boo()bar() 函数,但它们都在操作同一个共享变量 counter。这实际上没有提供任何保护,因为两个协程仍然可以同时访问 counter

  2. 竞态检测器未启用:你需要使用 -race 标志来编译和运行程序,才能检测到竞态条件。

下面是修复后的代码:

package main

import (
    "fmt"
    "runtime"
    "sync"
)

var counter int
var wg sync.WaitGroup
var mutex = sync.Mutex{}  // 使用同一个互斥锁

func main() {
    fmt.Println("CPU count Start: \t", runtime.NumCPU())
    fmt.Println("GoRoutines count Start: \t", runtime.NumGoroutine())

    wg.Add(2)

    go boo()
    go bar()

    wg.Wait()

    fmt.Println("GoRoutines count End: \t", runtime.NumGoroutine())
    fmt.Println("Final counter value: \t", counter)
    fmt.Println("the end : :)")
}

func boo() {
    defer wg.Done()
    for i := 0; i < 10; i++ {
        mutex.Lock()
        counter++
        fmt.Println("count of boo: \t", counter)
        mutex.Unlock()
    }
}

func bar() {
    defer wg.Done()
    for i := 0; i < 10; i++ {
        mutex.Lock()
        counter += 2
        fmt.Println("count of bar: \t", counter)
        mutex.Unlock()
    }
}

运行这个程序时使用竞态检测:

go run -race main.go

输出示例:

CPU count Start: 	 8
GoRoutines count Start: 	 1
count of boo: 	 1
count of bar: 	 3
count of boo: 	 4
count of bar: 	 6
count of boo: 	 7
count of bar: 	 9
count of boo: 	 10
count of bar: 	 12
count of boo: 	 13
count of bar: 	 15
count of boo: 	 16
count of bar: 	 18
count of boo: 	 19
count of bar: 	 21
count of boo: 	 22
count of bar: 	 24
count of bar: 	 26
count of boo: 	 27
GoRoutines count End: 	 1
Final counter value: 	 30
the end : :)

关键修改:

  1. 两个函数使用同一个 mutex 来保护 counter 变量
  2. 移除了函数返回值(因为 wg.Done() 在循环后执行)
  3. 使用 defer wg.Done() 确保函数退出时一定会调用
  4. 简化了循环变量的声明

这样修改后,两个协程会交替执行,每次对 counter 的访问都受到互斥锁的保护,最终 counter 的值会是 30(10次+1和10次+2)。

回到顶部