Derick
749 words
4 minutes
Go语言教程:并发编程

在本教程中,我们将通过两个示例代码来讲解Go语言中的并发编程。我们将深入浅出地讲解原理,帮助你更好地理解和应用Go语言的并发特性。

示例1:使用通道(Channel)进行并发控制#

代码解析#

package main

import (
	"fmt"
	//"time"
)

func main() {
	num := 10
	sign := make(chan struct{}, num)

	for i := 0; i < num; i++ {
		go func() {
			fmt.Println(i)
			sign <- struct{}{}
		}()
	}

	// 办法1。
	//time.Sleep(time.Millisecond * 500)

	// 办法2。
	for j := 0; j < num; j++ {
		<-sign
	}
}

代码讲解#

  1. 通道的创建

    sign := make(chan struct{}, num)
    
    

    这里我们创建了一个缓冲通道sign,缓冲大小为num(10)。通道用于在goroutine之间传递信号。

  2. 启动多个goroutine

    for i := 0; i < num; i++ {
        go func() {
            fmt.Println(i)
            sign <- struct{}{}
        }()
    }
    
    

    我们启动了num个goroutine,每个goroutine都会打印变量i的值,并向通道sign发送一个空结构体struct{}作为信号。

  3. 等待所有goroutine完成

    for j := 0; j < num; j++ {
        <-sign
    }
    
    

    通过从通道sign接收信号,我们确保所有goroutine都已经完成。

举一反三#

  • 通道的缓冲区:可以根据需要调整通道的缓冲区大小,以控制并发的数量。
  • 通道的类型:可以使用不同类型的通道来传递不同类型的数据。

示例2:使用原子操作进行并发控制#

代码解析#

package main

import (
	"fmt"
	"sync/atomic"
	"time"
)

func main() {
	var count uint32
	trigger := func(i uint32, fn func()) {
		for {
			if n := atomic.LoadUint32(&count); n == i {
				fn()
				atomic.AddUint32(&count, 1)
				break
			}
			time.Sleep(time.Nanosecond)
		}
	}
	for i := uint32(0); i < 10; i++ {
		go func(i uint32) {
			fn := func() {
				fmt.Println(i)
			}
			trigger(i, fn)
		}(i)
	}
	trigger(10, func() {})
}

代码讲解#

  1. 原子变量的使用

    var count uint32
    
    

    我们使用一个无符号32位整数count作为计数器,通过原子操作来保证并发安全。

  2. 触发函数**trigger**:

    trigger := func(i uint32, fn func()) {
        for {
            if n := atomic.LoadUint32(&count); n == i {
                fn()
                atomic.AddUint32(&count, 1)
                break
            }
            time.Sleep(time.Nanosecond)
        }
    }
    
    

    触发函数trigger会不断检查count的值是否等于i,如果相等则执行传入的函数fn,并将count加1。

  3. 启动多个goroutine

    for i := uint32(0); i < 10; i++ {
        go func(i uint32) {
            fn := func() {
                fmt.Println(i)
            }
            trigger(i, fn)
        }(i)
    }
    
    

    我们启动了10个goroutine,每个goroutine都会调用trigger函数,确保按顺序打印i的值。

  4. 等待所有goroutine完成

    trigger(10, func() {})
    
    

    最后,我们调用trigger(10, func() {})来等待所有goroutine完成。

拓展#

  • 原子操作:可以使用sync/atomic包中的其他原子操作函数,如atomic.StoreUint32atomic.CompareAndSwapUint32等。
  • 触发条件:可以根据需要修改触发条件,以实现不同的并发控制逻辑。

总结#

通过这两个示例,我们学习了如何使用通道和原子操作来进行并发控制。通道适用于需要在goroutine之间传递信号或数据的场景,而原子操作则适用于需要高效、低延迟的并发控制场景。

Go语言教程:并发编程
https://blog.ithuo.net/posts/go-concurrency-tutorial-1/
Author
Derick
Published at
2022-05-15