golang并发 | 青训营

64 阅读2分钟

Groutine

image.png

协程:用户态,轻量级线程,栈KB级别

线程:内核态,线程跑多个协程,栈MB级别

func hello(i int) {
	println("hello goroutine : " + fmt.Sprint(i))
}

func HelloGoRoutine() {
	for i := 0; i < 5; i++ {
		go func(j int) {
			hello(j)
		}(i)
	}
	time.Sleep(time.Second)
}
func hello(i int) {
	println("hello goroutine : " + fmt.Sprint(i))
}

func HelloGoRoutine() {
	for i := 0; i < 5; i++ {
		go hello(i)
	}
	time.Sleep(time.Second)
}

以上两个代码等效

CSP(communicating sequential processes)

image.png

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

Channel

make(chan type, [size])
// 无缓冲通道 make(chan int)
// 有缓冲通道 make(chan int, 2)

image.png

func CalSquare() {
	src := make(chan int)          // 创建一个无缓冲的整数类型通道 src
	dest := make(chan int, 3)     // 创建一个缓冲为 3 的整数类型通道 dest
	go func() {                   // 启动一个匿名的 goroutine
		defer close(src)          // 在 goroutine 结束时关闭 src 通道
		for i := 0; i < 10; i++ { // 迭代 0 到 9
			fmt.Printf("i : %v \n", i) // 打印当前迭代值 i
			time.Sleep(time.Second)    // 休眠一秒钟
			src <- i              // 将当前迭代值发送到 src 通道
		}
	}()

	go func() {                 // 启动另一个匿名的 goroutine
		defer close(dest)       // 在 goroutine 结束时关闭 dest 通道
		for i := range src {   // 从 src 通道接收值,直到通道关闭
			fmt.Printf("i*i : %v \n", i*i) // 打印接收到的值的平方
			time.Sleep(time.Second)    // 休眠一秒钟
			dest <- i * i       // 将接收到的值的平方发送到 dest 通道		
		}
	}()

	for i := range dest {    // 从 dest 通道接收值,直到通道关闭
		println(i)           // 打印接收到的值
	}
}

通过使用 Goroutine,这段代码实现了并发的数据流动。第一个 Goroutine 从 0 到 9 迭代并发送值到 src 通道,第二个 Goroutine 从 src 通道接收值并计算平方后发送到 dest 通道。主函数中的 for range dest 则从 dest 通道接收值并打印。这样,整个过程可以并发地执行,提高了程序的效率和响应能力。

在这段代码中,当协程执行 **defer close(src)** 的时候,**for i := range src** 才会退出循环

并发安全 Lock

var (
	x    int64
	lock sync.Mutex
)

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

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

func Add() {
	x = 0
	for i := 0; i < 5; i++ {
		go addWithoutLock()
	}
	time.Sleep(time.Second)
	fmt.Println("WithoutLock:", x)
	x = 0
	for i := 0; i < 5; i++ {
		go addWithLock()
	}
	time.Sleep(time.Second)
	println("WithLock:", x)
}
WithoutLock: 6574
WithLock: 10000

WaitGroup

sync.WaitGroup 是 Go 语言标准库中提供的一种机制,用于等待一组 Goroutine 完成其任务。

func hello(i int) {
    println("hello goroutine : " + fmt.Sprint(i))
}

func HelloGoRoutine() {
    var wg sync.WaitGroup    // 创建一个 WaitGroup 对象
    wg.Add(5)                // 增加 WaitGroup 的计数器为 5

    for i := 0; i < 5; i++ {
        go func(j int) {
            defer wg.Done()   // 当 Goroutine 完成任务时,调用 wg.Done() 减少计数器的值
            hello(j)
        }(i)
    }

    wg.Wait()                // 等待 WaitGroup 的计数器减少为 0,即所有 Goroutine 完成任务
}
  1. 首先创建了一个 sync.WaitGroup 对象 wg。然后,通过 wg.Add(5) 将计数器的值增加为 5,表示有 5 个 Goroutine 需要完成任务。
  2. 在每个 Goroutine 内部,通过 defer wg.Done() 来减少 WaitGroup 的计数器值。
  3. wg.Wait() 语句处等待 WaitGroup 的计数器值减少为 0。这将阻塞主 Goroutine,直到所有的 Goroutine 完成任务并调用 wg.Done() ,计数器的值减少为 0。然后程序继续执行。