Go语言并发之WaitGroup的用法详解

 更新时间:2023年06月09日 09:17:27   作者:zsx_yiyiyi  
这篇文章主要详细介绍了Go语言并发中得到WaitGroup,文中有相关的代码示例供大家参考,对我们的学习或工作有一定的参考价值,感兴趣的同学跟着小编一起来学习吧

1、Go语言并发之WaitGroup

goroutine 和 chan,一个用于并发,另一个用于通信。没有缓冲的通道具有同步的功能,除此之外,sync 包也提

供了多个 goroutine 同步的机制,主要是通过 WaitGroup 实现的。

WaitGroup 用来等待多个 goroutine 完成,main goroutine 调用 Add 设置需要等待 goroutine 的数目,每一个

goroutine 结束时调用 Done(),Wait() 被 main 用来等待所有的 goroutine 完成。

主要数据结构和操作如下:

type WaitGroup struct {
	// contains filtered or unexported fields
}
// 添加等待信号
func (wg*WaitGroup) Add (delta int)
// 释放等待信号
func (wg*WaitGroup) Done()
// 等待
func (wg*WaitGroup) Wait()

下面的程序演示如何使用 sync.WaitGroup 完成多个 goroutine 之间的协同工作。

package main
import (
	"net/http"
	"sync"
)
var wg sync.WaitGroup
var urls = []string{
	"https://news.sina.com.cn/",
	"https://www.bilibili.com/",
	"https://www.qq.com/",
}
func main() {
	for _, url := range urls {
		//每一个url启动一个goroutine,同时给wg加1
		wg.Add(1)
		// 启动一个goroutine获取URL
		go func(url string) {
			// 当前goroutine结束后给wg计数减1 ,wg.Done()等价于wg.Add(-1)
			// defer wg.Add(-1)
			defer wg.Done()
			// 发送http get请求并打印http返回码
			resp, err := http.Get(url)
			if err == nil {
				println(resp.Status)
			}
		}(url)
	}
	// 等待所有HTTP获取完成
	wg.Wait()
}

# 输出
501 Not Implemented
200 OK
200 OK

1.1 不加锁

多线程中使用睡眠函数不优雅,直接用 sync.WaitGroup 保证一个 goroutine 刚退出就可以继续执行,不需要自

己猜需要 sleep 多久。

package main
import (
	"fmt"
	"sync"
)
var wg sync.WaitGroup
func main() {
	// 启动一个goroutine就登记+1,启动十个就+10
	wg.Add(10)
	var count = 0
	for i := 0; i < 10; i++ {
		go func() {
			// goroutine结束就登记-1
			defer wg.Done()
			for j := 0; j < 100000; j++ {
				count++
			}
		}()
	}
	// 等待所有登记的goroutine都结束
	wg.Wait()
	// 346730
	fmt.Print(count)
}

启动十个goroutine对count自增10w次,理想状况为100w,但由于没有锁,会出现实际情况远远小于并且不相等

的情况。为什么会出现这样的结果?因为自增并不是一个原子操作,很可能几个goroutine同时读到同一个数,自

增,又将同样的数写了回去。

1.2 互斥锁

1.2.1 直接使用锁

共享资源是count变量,临界区是count++,临界区之前加锁,使其他goroutine在临界区阻塞,离开临界区解

锁,就可以解决这个 data race 的问题。go语言是通过 sync.Mutex 实现这一功能。

package main
import (
	"fmt"
	"sync"
)
var wg sync.WaitGroup
func main() {
	// 定义锁
	var mu sync.Mutex
	wg.Add(10)
	var count = 0
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			for j := 0; j < 100000; j++ {
				// 加锁
				mu.Lock()
				count++
				// 解锁
				mu.Unlock()
			}
		}()
	}
	wg.Wait()
	// 1000000
	fmt.Print(count)
}

1.2.2 嵌入字段方式使用锁

把 Mutex 嵌入 struct,可以直接在这个 struct 上使用 Lock/Unlock。

package main
import (
	"fmt"
	"sync"
)
var wg sync.WaitGroup
type Counter struct {
	sync.Mutex
	Count uint64
}
func main() {
	var counter Counter
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			for j := 0; j < 100000; j++ {
				counter.Lock()
				counter.Count++
				counter.Unlock()
			}
		}()
	}
	wg.Wait()
	// 1000000
	fmt.Print(counter.Count)
}

1.2.3 把加/解锁封装成方法

package main
import (
	"fmt"
	"sync"
)
var wg sync.WaitGroup
type Counter struct {
	mu    sync.Mutex
	count uint64
}
func (c *Counter) Incr() {
	c.mu.Lock()
	c.count++
	c.mu.Unlock()
}
func (c *Counter) Count() uint64 {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.count
}
func main() {
	var counter Counter
	// 启动一个goroutine就登记+1,启动十个就+10
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func() {
			// goroutine结束就登记-1
			defer wg.Done()
			for j := 0; j < 100000; j++ {
				counter.Incr()
			}
		}()
	}
	// 等待所有登记的goroutine都结束
	wg.Wait()
	// 1000000
	fmt.Print(counter.Count())
}

到此这篇关于Go语言并发之WaitGroup的用法详解的文章就介绍到这了,更多相关Go语言 WaitGroup内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • go语言调用其他包中的函数简单示例

    go语言调用其他包中的函数简单示例

    这篇文章主要给大家介绍了关于go语言调用其他包中的函数的相关资料,文中还介绍了Go语言同一个包中不同文件之间函数调用的相关问题,需要的朋友可以参考下
    2023-01-01
  • 如何让shell终端和goland控制台输出彩色的文字

    如何让shell终端和goland控制台输出彩色的文字

    这篇文章主要介绍了如何让shell终端和goland控制台输出彩色的文字的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • Go信号处理如何优雅地关闭你的应用

    Go信号处理如何优雅地关闭你的应用

    Go 中的优雅关闭机制使得在应用程序接收到终止信号时,能够进行平滑的资源清理,通过使用 context 来管理 goroutine 的生命周期,结合 signal 包捕获系统信号,你可以在 Go 应用中实现一个健壮且优雅的关闭过程,对Go关闭应用相关操作感兴趣的朋友一起看看吧
    2025-01-01
  • Go语言内建函数cap的实现示例

    Go语言内建函数cap的实现示例

    cap 是一个常用的内建函数,它用于获取某些数据结构的容量,本文主要介绍了Go语言内建函数cap的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2024-08-08
  • Go中的Timer 和 Ticker详解

    Go中的Timer 和 Ticker详解

    在日常开发中,我们可能会遇到需要延迟执行或周期性地执行一些任务,这个时候就需要用到 Go 语言中的定时器,本文将会对这两种定时器类型进行介绍,感兴趣的朋友一起看看吧
    2024-07-07
  • Go语言中的HTTP客户端从标准库到第三方库

    Go语言中的HTTP客户端从标准库到第三方库

    本文介绍了Go语言中的HTTP客户端开发,涵盖标准库和第三方库使用、最佳实践、高级功能、HTTP/2支持、安全考虑、性能优化等内容,通过本文,读者可以构建高效、可靠的HTTP客户端应用
    2026-05-05
  • Golang中defer与recover的组合使用示例代码

    Golang中defer与recover的组合使用示例代码

    Go语言的defer和recover机制为开发者提供了一种优雅处理错误的方式,帮助保持程序的稳定性和可维护性,这篇文章主要介绍了Golang中defer与recover组合使用的相关资料,需要的朋友可以参考下
    2025-06-06
  • 在Go中格式化字符串的几种常用方法

    在Go中格式化字符串的几种常用方法

    Go对字符串格式化提供了良好的支持,这篇文章主要给大家介绍了关于在Go中格式化字符串的几种常用方法,文中通过代码介绍的非常详细,对大家学习或者使用Go具有一定的参考价值,需要的朋友可以参考下
    2023-10-10
  • golang多次读取http request body的问题分析

    golang多次读取http request body的问题分析

    这篇文章主要给大家分析了golang多次读取http request body的问题,文中通过代码示例和图文介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-01-01
  • Go语言遍历循环的几种方法

    Go语言遍历循环的几种方法

    遍历循环主要用于迭代数组、切片、映射(map)、字符串等数据结构,本文主要介绍了Go语言遍历循环的几种方法,下面就来介绍一下,具有一定的参考价值,感兴趣的可以了解一下
    2025-03-03

最新评论