Go并发笔记 | 青训营

75 阅读2分钟

CSP(Communication Sequential Process)

提倡通过通信共享内存,而不是通过共享内存实现通信

image.png

Channel 应用

以下代码展示了使用goroutine和通道来实现一个简单的平方计算过程

func calSquare() {
    src := make(chan int)
    dest := make(chan []int, 3)

    A := func() {
        defer close(src)
        for i := 0; i < 10; i++ {
            src <- i
        }
    }

    B := func() {
        defer close(dest)
        for i := range src {
            dest <- []int{i, i * i}
        }
    }

    go A()
    go B()


    for i := range dest {
        fmt.Println(i[0], "的平方是", i[1])
    }
}

func main() {
    calSquare()
}

输出结果

0 的平方是 0
1 的平方是 1
2 的平方是 4
3 的平方是 9
4 的平方是 16
5 的平方是 25
6 的平方是 36
7 的平方是 49
8 的平方是 64
9 的平方是 81

这个例子展示了如何使用goroutine和通道来实现并发计算,并在并发的计算结果中进行同步

这种机制允许我们在并发场景下实现更高效和灵活的计算过程

Lock 应用

以下代码涉及了并发场景下对共享变量的操作,并使用了互斥锁(sync.Mutex)来保护共享变量

var (
        // 共享变量
        x int64
        // 互斥锁
        lock sync.Mutex
)

func addWithoutLock() {
        for i := 0; i < 2000; i++ {
                x += 1
        }
}

func addWithLock() {
        for i := 0; i < 2000; i++ {
                lock.Lock()
                // 临界区
                x += 1
                // 临界区
                lock.Unlock()
        }
}

func add() {
        x = 0
        for i := 0; i < 5; i++ {
                go addWithoutLock()
        }
        time.Sleep(time.Second)
        fmt.Println("without lock:", x) // 没有加锁保护,值不确定

        x = 0
        for i := 0; i < 5; i++ {
                go addWithLock()
        }
        time.Sleep(time.Second)
        fmt.Println("with lock:", x) // 每次只有一个goroutine可以修改x的值
}

func main() {
        add()
}

输出结果

without lock: 9127 // 没有锁保护,值异常
with lock: 10000

在实际开发中,对共享变量的访问需要谨慎处理,特别是在并发环境下

使用互斥锁来保护共享变量是一种常见的方式,但也需要注意避免死锁和过多的锁竞争,以提高程序的性能和可维护性

WaitGroup 应用

以下代码展示了使用 goroutine 和 sync.WaitGroup 来创建多个并发的打印操作

func hello(i int) {
        fmt.Println("hello", i)
}

func helloGoroutine() {
        n := 5
        var wg sync.WaitGroup
        wg.Add(n)

        for i := 0; i < n; i++ {
                go func(j int) {
                        defer wg.Done()
                        hello(j) // goroutine 并发执行,打印的结果随机
                }(i)
        }

        wg.Wait() // wg 将在 n 个 goroutine 完成之后解除阻塞
}

func main() {
        helloGoroutine()
}

输出结果

hello 0
hello 4
hello 1
hello 2
hello 3

执行wg.Done()后,WaitGroup 中的计数器 -1

如果在计数器为 0 时调用wg.Done(),会引发 panic,因为计数器的值不能为负

如果没有wg.Wait()的等待操作,程序将在创建了 n 个 gocroutine 后直接结束,没有机会输出结果

WaitGroup 允许我们在并发场景下有效地控制和同步多个 goroutine 的执行,以实现更高效和并发的程序