Go语言基础之14--Waitgroup和原子操作
一、Waitgroup介绍
1.1 背景
package main import (
"fmt"
"time"
) func main() {
ch := make(chan string)
go sendData(ch)
go getData(ch)
time.Sleep( * time.Second)
}
func sendData(ch chan string) {
ch <- "Washington"
ch <- "Tripoli"
ch <- "London"
ch <- "Beijing"
ch <- "Tokio"
}
func getData(ch chan string) {
var input string
for {
input = <-ch
fmt.Println(input)
}
}
会有一个问题,如果sleep时间都结束了,但是sendData和getdata所在的函数还没执行完,那么也会被中断执行,如何解决呢:
解决办法:
1、死循环:( 缺点:有时生产者和消费者已经执行完,却依然还在死循环,退不出。)
2、标识位,也就是全局变量和加锁(缺点:比较麻烦,如果有100个goroutine,也要写100个标识位)
上述2个办法都太麻烦不可取,可以pass掉了,下面我们有更好办法:
如何等待一组goroutine结束?
有下面2中方法,GO语言提供了2种方法Channel和WaitGroup来解决goroutine同步和通讯,我们还是比较推荐第二种WaitGroup
补充:
https://studygolang.com/articles/9173
1.2 方法一,使用不带缓冲区的channel实现
带缓冲区也是可以的
实例如下:
package main import (
"fmt"
"time"
) func process(i int, ch chan bool) {
fmt.Println("started Goroutine ", i)
time.Sleep( * time.Second)
fmt.Printf("Goroutine %d ended\n", i)
ch <- true
}
func main() {
no :=
exitChan := make(chan bool, no)
for i := ; i < no; i++ {
go process(i, exitChan)
}
for i := ; i < no; i++ {
<-exitChan
}
fmt.Println("All go routines finished executing")
}
执行结果如下:

1.3 方法二,使用sync.WaitGroup实现
package main import (
"fmt"
"sync"
"time"
) func process(i int, wg *sync.WaitGroup) {
fmt.Println("started Goroutine ", i)
time.Sleep( * time.Second)
fmt.Printf("Goroutine %d ended\n", i)
wg.Done()
}
func main() {
no :=
var wg sync.WaitGroup
for i := ; i < no; i++ {
wg.Add()
go process(i, &wg)
}
wg.Wait()
fmt.Println("All go routines finished executing")
}
执行结果如下:

1.4 补充实例
1.4.1 方法1:channel
代码实例:
package main import (
"fmt"
// "time"
) func main() {
ch := make(chan string)
exitChan := make(chan bool, ) //此例我们有3个goroutine,所以我们定义一个长度为3的channel,当我的channel中可以读取到3个元素时,即表示3个goroutine都执行完毕了。
go sendData(ch, exitChan) //每一个goroutine执行结束时,往channel中插入一个数据
go getData(ch, exitChan)
go getData2(ch, exitChan) //等待其他goroutine退出,当goroutine都执行完毕退出之后,channel中有3个元素,我们可以做一个取3次的操作,当3次都取完了,表示所有goroutine都退出了
<-exitChan //从channel中取出来元素并未赋值给任何变量,就相当于丢弃了
<-exitChan
<-exitChan
fmt.Printf("main goroutine exited\n")
} func sendData(ch chan string, exitCh chan bool) {
ch <- "aaa"
ch <- "bbb"
ch <- "ccc"
ch <- "ddd"
ch <- "eee"
close(ch) //插入数据结束后,关闭管道channnel
fmt.Printf("send data exited")
exitCh <- true //此时已经往goroutine中插入数据结束,goroutine退出之前,往我们定义的channel中插入一个数据true,相当于告知我已经执行完成
} func getData(ch chan string, exitCh chan bool) {
//var input string
for {
//input = <- ch
input, ok := <-ch //检查管道是否被关闭
if !ok { //如果被关闭了,ok=false,我们就break退出
break
}
// 此处 打印出来的顺序 和写入的顺序 是一致的
// 遵循队列的原则: 先入先出
fmt.Printf("getData中的input值:%s\n", input)
}
fmt.Printf("get data exited\n")
exitCh <- true
} func getData2(ch chan string, exitCh chan bool) {
//var input2 string
for {
//input2 = <- ch
input2, ok := <-ch
if !ok {
break
}
// 此处 打印出来的顺序 和写入的顺序 是一致的
// 遵循队列的原则: 先入先出
fmt.Printf("getData2中的input值:%s\n", input2)
}
fmt.Printf("get data2 exited\n")
exitCh <- true
}
执行结果如下:

注意:当我们为channel中放入10个元素,然后把channel关闭,这些元素还是在channel中的,不会消失的,之后想取还是可以取出来的。
1.4.2 方法2:Waitgroup(推荐)
针对大批量goroutine,用sync包中的waitGroup方法,其本身是一个结构体,该方法的本质在底层就是一个计数。
代码实例如下:
package main import (
"fmt"
"sync"
// "time"
) func main() {
var wg sync.WaitGroup //定义一个waitgroup(结构体)类型的变量,针对大批量goroutine时比较方便。
ch := make(chan string)
wg.Add() //3个goroutine,就传入3,Add方法相当于计数
go sendData(ch, &wg) //,相当于goroutine执行完,Add计数就减1,所以我们将wg传入,但注意结构体必须要传入一个地址进去
go getData(ch, &wg)
go getData2(ch, &wg) wg.Wait() //只要Add中计数依然存在,就一直Wait,除非为0
fmt.Printf("main goroutine exited\n")
} func sendData(ch chan string, waitGroup *sync.WaitGroup) {
ch <- "aaa"
ch <- "bbb"
ch <- "ccc"
ch <- "ddd"
ch <- "eee"
close(ch)
fmt.Printf("send data exited")
waitGroup.Done() //goroutine退出时,计数减1,所以这里用Done方法来通知Add方法
} func getData(ch chan string, waitGroup *sync.WaitGroup) {
//var input string
for {
//input = <- ch
input, ok := <-ch
if !ok {
break
}
// 此处 打印出来的顺序 和写入的顺序 是一致的
// 遵循队列的原则: 先入先出
fmt.Printf("getData中的input值:%s\n", input)
}
fmt.Printf("get data exited\n")
waitGroup.Done()
} func getData2(ch chan string, waitGroup *sync.WaitGroup) {
//var input2 string
for {
//input2 = <- ch
input2, ok := <-ch
if !ok {
break
}
// 此处 打印出来的顺序 和写入的顺序 是一致的
// 遵循队列的原则: 先入先出
fmt.Printf("getData2中的input值:%s\n", input2)
}
fmt.Printf("get data2 exited\n")
waitGroup.Done()
}
执行结果如下:

二、原子操作
主要还是为了解决线程安全的问题。
2.1 介绍
A. 加锁代价比较耗时,需要上下文切换
B. 针对基本数据类型,可以使用原子操作保证线程安全
C. 原子操作在用户态就可以完成,因此性能比互斥锁要高
D.针对特定需求,原子操作一步就可以操作完成,而加锁就需要好几步(加锁-操作-解锁)
2.2 适用范围
原子操作适用于一些简单操作的数据类型,对于复杂数据类型还是需要借助锁。

2.3 实例
有计数的需求,可以采用原子操作;
package main import (
"fmt"
"sync/atomic" //原子操作需要借助aync中的atomic包
"time"
) var count int32 //var mutex sync.Mutex func test1() {
for i := ; i < ; i++ {
/* 注释掉的这部分是如果采用加锁操作写法
mutex.Lock()
count++
mutex.Unlock()
*/
atomic.AddInt32(&count, ) //AddInt32函数的第一个参数是传入要修改的变量的地址,第二个参数是要加多少,这样我们就可以借助原子进行操作,而不是加锁了。
}
} func test2() {
for i := ; i < ; i++ {
/* 注释掉的这部分是如果采用加锁操作写法
mutex.Lock()
count++
mutex.Unlock()
*/
atomic.AddInt32(&count, )
}
} func main() {
go test1()
go test2() time.Sleep(time.Second)
fmt.Printf("count=%d\n", count)
}
执行结果:

解释:
我们可以发现最终结果是2000000,证明在不加锁状态下,依靠原子操作也实现了线程安全。
Go语言基础之14--Waitgroup和原子操作的更多相关文章
- GO学习-(14) Go语言基础之接口
Go语言基础之接口 接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节. 接口 接口类型 在Go语言中接口(interface)是一种类型,一种抽象的类 ...
- Go语言基础之并发
并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因. Go语言中的并发编程 并发与并行 并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天) ...
- GO学习-(18) Go语言基础之并发
Go语言基础之并发 并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因. Go语言中的并发编程 并发与并行 并发:同一时间段内执行多个任务(你在用微 ...
- 《MSSQL2008技术内幕:T-SQL语言基础》读书笔记(下)
索引: 一.SQL Server的体系结构 二.查询 三.表表达式 四.集合运算 五.透视.逆透视及分组 六.数据修改 七.事务和并发 八.可编程对象 五.透视.逆透视及分组 5.1 透视 所谓透视( ...
- C#语言基础
第一部分 了解C# C#是微软公司在2000年7月发布的一种全新且简单.安全.面向对象的程序设计语言,是专门为.NET的应用而开发的.体现了当今最新的程序设计技术的功能和精华..NET框架为C#提供了 ...
- C语言基础回顾
第一章 C语言基础 1. C语言编译过程 预处理:宏替换.条件编译.头文件包含.特殊符号 编译.优化:翻译并优化成等价的中间代码表示或汇编代码 汇编:生成目标文件,及与源程序等效的目标的机器语言代码 ...
- R语言基础:数组&列表&向量&矩阵&因子&数据框
R语言基础:数组和列表 数组(array) 一维数据是向量,二维数据是矩阵,数组是向量和矩阵的直接推广,是由三维或三维以上的数据构成的. 数组函数是array(),语法是:array(dadta, d ...
- php面试题之三——PHP语言基础(基础部分)
三.PHP语言基础 1. strlen( )与 mb_strlen( )的作用分别是什么(新浪网技术部) strlen和mb_strlen都是用于获取字符串长度. strlen只针对单字节编码字符,也 ...
- Object Pascal 语言基础
Delphi 是以Object Pascal 语言为基础的可视化开发工具,所以要学好Delphi,首先要掌握的就是Object Pascal 语言.Object Pascal语言是Pascal之父在1 ...
- C#语言基础——7月21日
C#语言基础 一.语言基础 (一).函数的四要素: 名称,输入,输出,加工(二).主函数.输出语句.输入语句: Static void Main(string[] args)//下划 ...
随机推荐
- css中的hack
1.什么是CSS hack? CSS hack是通过在CSS样式中加入一些特殊的符号,让不同的浏览器识别不同的符号(什么样的浏览器识别什么样的符号是有标准的,CSS hack就是让你记住这个标准),以 ...
- 由浅入深漫谈margin属性
margin 在中文中我们翻译成外边距或者外补白(本文中引用外边距).他是元素盒模型(box model)的基础属性. 一.margin的基本特性 margin 属性包括 margin-top, ma ...
- day69-oracle 22-DBCA
只涉及到数据库的管理,不涉及到数据库的开发.不涉及到写SQL程序或者是写增删改查,不涉及到这些东西,也不涉及到事务. 你在安装oracle的时候它自动帮你创建一个数据库.
- [Elasticsearch2.x] 多字段搜索 (二) - 最佳字段查询及其调优 <译>
最佳字段(Best Fields) 假设我们有一个让用户搜索博客文章的网站,就像这两份文档一样: PUT /my_index/my_type/ { "title": "Q ...
- Angular问题03 @angular/material版本问题
1 问题描述 应用使用 angular4在使用@angular/material时,若果在导入模块时使用mat开头,就会报错. 2 问题原因 @angular/material版本出现问题,@angu ...
- zedboard:使用ISE和modelsim搭建仿真环境 标签: zedboardfpgamodelsimise 2017-03-03 14:00 528人阅读
详细步骤: 产生ISE仿真库文件 开始->所有程序->xilinx design tools->simulation library compilation wizard.路径可能不 ...
- eval实例
.... var sel_MedicineType = 'sel_MedicineType' + lastIndex; eval(sel_MedicineType + "= new C_Se ...
- 微信开放平台 redirect_uri参数错误
微信开放平台 redirect_uri参数错误 请注意是开放平台开放平台,公众平台和开放平台不是同一个. 解决办法 在写 授权回调域 时,地址只用写到域名级,不能写到域名下一级,这和QQ互联的回调 ...
- loj #2008. 「SCOI2015」小凸想跑步
#2008. 「SCOI2015」小凸想跑步 题目描述 小凸晚上喜欢到操场跑步,今天他跑完两圈之后,他玩起了这样一个游戏. 操场是个凸 n nn 边形,N NN 个顶点按照逆时针从 0∼n−1 0 ...
- 函数声明后面的const用法
void function() const{} 通常我们会看到一些函数声明后面会跟着一个const,这个const是做什么的呢? 看一下下面的例子,就知道了.直接在编译前,就会提示下面的两个错误 // ...