【Go学习笔记12】并发(二)

本文深入讲解Go语言中的WaitGroup和通道机制,包括无缓冲和有缓冲通道的应用场景及其实现方式,通过实例演示如何利用这些工具高效管理goroutine。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

WaitGroup介绍

在go语言中,WaitGroup是用来表示一组等待goroutine运行结束的集合。你可以在主goroutine中使用Add方法添加需要等待的goroutine数量,在其它的goroutine结束的时候,需要调用Done方法来通知已经执行结束。同时,Wait方法会阻塞代码的执行直到所有的goroutine都执行完成。官方文档请点

使用WaitGroup等待goroutine结束

在上一篇文章中,我们使用了time.Sleep来阻塞主goroutine的执行,等待其它goroutine执行结束。现在我们来使用WaitGroup来实现:

package main

import (
	"sync"
	"fmt"
)

//使用WaitGroup来等待goroutine结束
var wait sync.WaitGroup

func A(){
	fmt.Println("A执行完成。。")
	wait.Done()
}

func main(){
	//增加1,表示需要等待一个goroutine执行结束
	wait.Add(1)
	go A()
	//等待goroutine结束
	wait.Wait()
	fmt.Println("main函数执行完成")
}
//A执行完成。。
//main函数执行完成
复制代码

可以看到使用了上面的代码来等待goroutine完成更加准确,而不像time.Sleep那样可能会出现等待时间之后goroutine还没有结束或者等待时间过长的问题。

从上面的代码,我们可以可以看到WaitGroup其实是使用了信号量的机制来实现的。

通道

在go语言中,通道被用来在多个goroutine之间同步数据。在上一篇文章中介绍的原子操作和同步锁解决数据争用的问题。同样可以使用通道来解决,而且通道更加方便,容易使用。当需要在多个goroutine中共享资源的时候,通道可以在goroutine之间传递数据,并且可以确保这个过程是同步的。而通道又分为有无缓冲通道和有缓冲通道。

无缓冲通道

无缓冲通道在收到数据时立即传递给另外一个goroutine,不会缓冲数据。这就需要发送数据的goroutine和接收数据的goroutine同时准备好。否则就会阻塞。

//创建一个无缓冲通道
buffer := make(chan int)
复制代码

使用内置的make函数来创建通道,第一个参数需要使用关键字chan,之后是该通道可以传递的数据类型。上面的例子表示创建了一个可以传递int类型数据的无缓冲通道。

package main

import (
	"sync"
	"fmt"

)

func main(){
	//创建一个WaitGroup
	var wait sync.WaitGroup
	//信号量+2,表示等待两个goroutine完成
	wait.Add(2)

	buffer := make(chan int)
	go func(){
		fmt.Println("将数据放入通道。")
		//将12放入通道
		buffer <- 12
		//完成
		wait.Done()
	}()

	go func(){
		//从通道中获取数据
		fmt.Println("从通道拿到数据:",<- buffer)
		//关闭通道
		close(buffer)
		//完成
		wait.Done()
	}()
	//等待两个goroutine完成
	wait.Wait()
	fmt.Println("main函数执行完成。")
}
//将数据放入通道。
//从通道拿到数据: 12
//main函数执行完成。
复制代码

上面的代码,使用通道将数据从一个goroutine传递到了另外一个goroutine。我们也可以在两个goroutine之间相互传递数据。来看下面的代码:

package main

import (
	"sync"
	"fmt"

)

func main(){
	//创建一个WaitGroup
	var wait sync.WaitGroup
	//信号量+2,表示等待两个goroutine完成
	wait.Add(2)

	buffer := make(chan int)
	go func(name string){
		fmt.Println("将数据放入通道。")
		//将12放入通道
		buffer <- 12

		fmt.Println(name,"从通道拿到数据:",<-buffer)
		//完成
		wait.Done()
	}("A")

	go func(name string){
		//从通道中获取数据
		value := <- buffer
		fmt.Println(name,"从通道拿到数据:",value)
		//将value1
		value++

		//重新放入通道
		buffer <- value

		//完成
		wait.Done()
	}("B")
	//等待两个goroutine完成
	wait.Wait()
	//关闭通道
	close(buffer)
	fmt.Println("main函数执行完成。")
}
//将数据放入通道。
//B 从通道拿到数据: 12
//A 从通道拿到数据: 13
//main函数执行完成。
复制代码

从上面的代码执行结果可以看出,我们可以在两个goroutine之间交换数据,并且通道保证了数据的同步。

有缓冲通道

有缓冲通道表示,在通道接收到数据的时候会保留到缓冲区,等待接收goroutine的读取。只有在缓冲区满了之后,发送goroutine才会阻塞,而只有缓冲区无数据的时候,接收goroutine才会阻塞。

//创建一个有缓冲通道,缓冲区的大小为10
buffer := make(chan int,10)
复制代码

上面的代码使用内置函数make创建了一个有缓冲的通道,缓冲的大小为10。

package main

import (
	"time"
	"sync"
	"fmt"
)

var wait sync.WaitGroup

func Producer(channel chan int){
	for i:= 1 ; i<10 ;i++{
		//休眠100毫秒
		time.Sleep(100 * time.Millisecond)

		//向通道中发送数据
		channel <- i
		fmt.Println("生产者生产:",i)
	}
	//生产完成,关闭通道
	close(channel)
	wait.Done()
}

func Consumer(channel chan int){
  //延迟执行
	defer wait.Done()
	
	for {
		//从通道中读取数据
		value ,ok := <- channel

    //当通道关闭的时候返回
		if !ok {
			return
		}
		//休眠150毫秒
		time.Sleep(150 * time.Millisecond)
		fmt.Println("消费者消费:",value)
	}
}

func main(){
	//创建一个有缓冲通道,缓冲区的大小为10
	buffer := make(chan int,10)

	wait.Add(2)

	go Producer(buffer)
	go Consumer(buffer)

	wait.Wait()
	
	fmt.Println("mian函数执行完成")
}
//生产者生产: 1
//生产者生产: 2
//消费者消费: 1
//生产者生产: 3
//消费者消费: 2
//生产者生产: 4
//生产者生产: 5
//消费者消费: 3
//生产者生产: 6
//消费者消费: 4
//生产者生产: 7
//生产者生产: 8
//消费者消费: 5
//生产者生产: 9
//消费者消费: 6
//消费者消费: 7
//消费者消费: 8
//消费者消费: 9
//mian函数执行完成
复制代码

上面的代码模拟了生产者消费者模式,生产者往通道发送数据,而消费者从通道中获取数据,当生产者生产完成之后,关闭通道。

转载于:https://juejin.im/post/5a2b4e0a51882575cb740761

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值