Go语言并发之WaitGroup

这篇具有很好参考价值的文章主要介绍了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。文章来源地址https://www.toymoban.com/news/detail-476077.html

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的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 7 文件操作、单元测试、goroutine【Go语言教程】

    1.1 介绍 os.File 封装所有文件相关操作,File 是一个结构体 常用方法: 打开文件 关闭文件 1.2 应用实例 ①读文件 常用方法: ①bufio.NewReader(), reader.ReadString【带缓冲】 ②io/ioutil【一次性读取,适用于小文件】 读取文件的内容并显示在终端(带缓冲区的方式),使用 os.Open, file.

    2024年02月04日
    浏览(43)
  • Go语言入门记录:从基础到变量、函数、控制语句、包引用、interface、panic、go协程、Channel、sync下的waitGroup和Once等

    程序入口文件的包名必须是main,但主程序文件所在文件夹名称不必须是 main ,即我们下图 hello_world.go 在 main 中,所以感觉 package main 写顺理成章,但是如果我们把 main 目录名称改成随便的名字如 filename 也是可以运行的,所以迷思就在于写在文件开头的那个 package main 和 java

    2024年02月11日
    浏览(24)
  • Golang之路---04 并发编程——WaitGroup

    为了保证 main goroutine 在所有的 goroutine 都执行完毕后再退出,前面使用了 time.Sleep 这种简单的方式。 由于写的 demo 都是比较简单的, sleep 个 1 秒,我们主观上认为是够用的。 但在实际开发中,开发人员是无法预知,所有的 goroutine 需要多长的时间才能执行完毕,sleep 多了,

    2024年02月14日
    浏览(24)
  • 深入理解 go sync.Waitgroup

    本文基于 Go 1.19。 go 里面的 WaitGroup 是非常常见的一种并发控制方式,它可以让我们的代码等待一组 goroutine 的结束。 比如在主协程中等待几个子协程去做一些耗时的操作,如发起几个 HTTP 请求,然后等待它们的结果。 下面的代码展示了一个 goroutine 等待另外 2 个 goroutine 结束

    2024年01月16日
    浏览(27)
  • 控制goroutine 的并发执行数量

    正常项目,协程数量超过十万就需要引起重视。如果有上百万goroutine,一般是有问题的。 但并不是说协程数量的上限是100多w 1048575的来自类似如下的demo代码: 执行后,很快报错 panic: too many concurrent operations on a single file or socket (max 1048575) 但这个是因为fmt.Printf导致的: 对单个

    2024年02月11日
    浏览(26)
  • 掌握Go并发:Go语言并发编程深度解析

    🏷️ 个人主页 :鼠鼠我捏,要死了捏的主页  🏷️ 系列专栏 :Golang全栈-专栏 🏷️ 个人学习笔记,若有缺误,欢迎评论区指正   前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站AI学习网站。 当我们开发一个W

    2024年02月20日
    浏览(35)
  • Go语言并发

    出色的并发性是Go语言的特色之一 • 理解并发与并行 • 理解进程和线程 • 掌握Go语言中的Goroutine和channel • 掌握select分支语句 • 掌握sync包的应用 并发与并行的概念这里不再赘述, 可以看看之前java版写的并发实践; 程序、进程与线程这里也不赘述 一个进程可以包括多个线

    2024年02月06日
    浏览(22)
  • 【Go】Go语言并发编程:原理、实践与优化

    在当今的计算机世界,多核处理器和并发编程已经成为提高程序执行效率的关键。Go语言作为一门极富创新性的编程语言,凭借其强大的并发能力,在这方面表现出色。本文将深入探讨Go语言并发编程的原理,通过实际代码示例展示其应用,并讨论可能的优化策略。 在了解G

    2024年02月10日
    浏览(45)
  • 掌握Go语言:Go语言通道,并发编程的利器与应用实例(20)

    通道(Channel)是用来在 Go 程序中传递数据的一种数据结构。它是一种类型安全的、并发安全的、阻塞式的数据传输方式,用于在不同的 Go 协程之间传递消息。 基本概念 创建通道 :使用 make() 函数创建一个通道。 发送数据 :使用 - 操作符向通道发送数据。 接收数据 :使用

    2024年03月21日
    浏览(48)
  • Go语言并发模式视角思考

    犹记得2019年中旬进行知识点的学习和demo的练习,熟悉各种语法和并发调度的场景, 在2019年末开始参与项目实战开发和逻辑梳理 Go语言的接触也是更多探索和业务的拆件,做一些雏形工具,来慢慢的孵化业务生态 后来陆陆续续,在主营业务是PHP的情况下,尽量在业务脚本的

    2024年01月21日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包