go语言中sync包和channel机制
文章转载至:https://www.bytelang.com/article/content/A4jMIFmobcA=
golang中实现并发非常简单,只需在需要并发的函数前面添加关键字"Go",但是如何处理go并发机制中不同goroutine之间的同步与通信,golang 中提供了sync包和channel机制来解决这一问题.
sync 包提供了互斥锁这类的基本的同步原语.除 Once 和 WaitGroup 之外的类型大多用于底层库的例程。更高级的同步操作通过信道与通信进行。
type Cond
func NewCond(l Locker) *Cond
func (c *Cond) Broadcast()
func (c *Cond) Signal()
func (c *Cond) Wait()
type Locker
type Mutex
func (m *Mutex) Lock()
func (m *Mutex) Unlock()
type Once
func (o *Once) Do(f func())
type Pool
func (p *Pool) Get() interface{}
func (p *Pool) Put(x interface{})
type RWMutex
func (rw *RWMutex) Lock()
func (rw *RWMutex) RLock()
func (rw *RWMutex) RLocker() Locker
func (rw *RWMutex) RUnlock()
func (rw *RWMutex) Unlock()
type WaitGroup
func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()
而golang中的同步是通过sync.WaitGroup来实现的.WaitGroup的功能:它实现了一个类似队列的结构,可以一直向队列中添加任务,当任务完成后便从队列中删除,如果队列中的任务没有完全完成,可以通过Wait()函数来出发阻塞,防止程序继续进行,直到所有的队列任务都完成为止.
WaitGroup总共有三个方法:Add(delta int), Done(), Wait()。Add:添加或者减少等待goroutine的数量Done:相当于Add(-1)Wait:执行阻塞,直到所有的WaitGroup数量变成0
具体例子如下:
package main import (
"fmt"
"sync"
) var waitgroup sync.WaitGroup func Afunction(shownum int) {
fmt.Println(shownum)
waitgroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)
} func main() {
for i := 0; i < 10; i++ {
waitgroup.Add(1) //每创建一个goroutine,就把任务队列中任务的数量+1
go Afunction(i)
}
waitgroup.Wait() //.Wait()这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞
}
在线示例:https://www.bytelang.com/o/s/c/6z7UkvezTJg=
使用场景:
程序中需要并发,需要创建多个goroutine,并且一定要等这些并发全部完成后才继续接下来的程序执行.WaitGroup的特点是Wait()可以用来阻塞直到队列中的所有任务都完成时才解除阻塞,而不需要sleep一个固定的时间来等待.但是其缺点是无法指定固定的goroutine数目.
Channel机制:
相对sync.WaitGroup而言,golang中利用channel实习同步则简单的多.channel自身可以实现阻塞,其通过<-进行数据传递,channel是golang中一种内置基本类型,对于channel操作只有4种方式:
创建channel(通过make()函数实现,包括无缓存channel和有缓存channel);
向channel中添加数据(channel<-data);
从channel中读取数据(data<-channel);
关闭channel(通过close()函数实现,关闭之后无法再向channel中存数据,但是可以继续从channel中读取数据)
channel分为有缓冲channel和无缓冲channel,两种channel的创建方法如下:
var ch = make(chan int) //无缓冲channel,等同于make(chan int ,0)
var ch = make(chan int,10) //有缓冲channel,缓冲大小是5
其中无缓冲channel在读和写是都会阻塞,而有缓冲channel在向channel中存入数据没有达到channel缓存总数时,可以一直向里面存,直到缓存已满才阻塞.由于阻塞的存在,所以使用channel时特别注意使用方法,防止死锁的产生.例子如下:
无缓存channel:
package main
import "fmt"
func Afuntion(ch chan int) {
fmt.Println("finish")
<-ch
}
func main() {
ch := make(chan int) //无缓冲的channel
go Afuntion(ch)
ch <-
// 输出结果:
// finish
}
在线示例:https://www.bytelang.com/o/s/c/3cxH7Jko7YY=
代码分析:首先创建一个无缓冲channel ch, 然后执行 go Afuntion(ch),此时执行<-ch,则Afuntion这个函数便会阻塞,不再继续往下执行,直到主进程中ch<-1向channel ch 中注入数据才解除Afuntion该协程的阻塞.
更正:
代码分析:对于该段程序(只有单核cpu运行的程序)首先创建一个无缓冲channel ch,然后遇到go Afuntion(ch),查看此时无cpu可以用来运行该任务,则将该任务记下,等到有cpu时再运行该任务,然后执行ch<-1,此时主goroutine阻塞,查找是否有其他协程,查找到有Afuntion(ch)这一goroutine,则执行该goroutine内容,直到<-ch才从主goroutine获取数据1,解除主goroutine阻塞.(注:这种执行方式仅限于单核cpu)
如果指定多个cpu运行,则首先运行主goroutine创建无缓冲的channel,然后查看是否有空闲cpu可以运行另外一个goroutine,如果有,则运行协程Afuntion(ch),对于多核cpu,主goroutine和另外一个goroutine的运行顺序是不确定的.
package main
import "fmt"
import "runtime"
import "time" func Afuntion(ch chan int) {
fmt.Println("finish")
<-ch
} func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
ch := make(chan int) //无缓冲的channel
go Afuntion(ch)
time.Sleep(time.Nanosecond * )
fmt.Println("main goroutine")
ch <-
}
在线示例:https://www.bytelang.com/o/s/c/9z_uWI5ZumA=
运行结果:
finishmain goroutine
或者 main goroutine
finish
主goroutine和另外一个goroutine的执行顺序是不确定的(对于多核cpu)
package main
import "fmt"
func Afuntion(ch chan int) {
fmt.Println("finish")
<-ch
}
func main() {
ch := make(chan int) //无缓冲的channel
//只是把这两行的代码顺序对调一下
ch <-
go Afuntion(ch)
// 输出结果:
// 死锁,无结果
}
在线示例:https://www.bytelang.com/o/s/c/sLL_Cto3k4E=
代码分析:首先创建一个无缓冲的channel, 然后在主协程里面向channel ch 中通过ch<-1命令写入数据,则此时主协程阻塞,就无法执行下面的go Afuntions(ch),自然也就无法解除主协程的阻塞状态,则系统死锁
总结:
对于无缓存的channel,放入channel和从channel中向外面取数据这两个操作不能放在同一个协程中,防止死锁的发生;同时应该先利用go 开一个协程对channel进行操作,此时阻塞该go 协程,然后再在主协程中进行channel的相反操作(与go 协程对channel进行相反的操作),实现go 协程解锁.即必须go协程在前,解锁协程在后.
带缓存channel:
对于带缓存channel,只要channel中缓存不满,则可以一直向 channel中存入数据,直到缓存已满;同理只要channel中缓存不为0,便可以一直从channel中向外取数据,直到channel缓存变为0才会阻塞.
由此可见,相对于不带缓存channel,带缓存channel不易造成死锁,可以同时在一个goroutine中放心使用,
close():
close主要用来关闭channel通道其用法为close(channel),并且实在生产者的地方关闭channel,而不是在消费者的地方关闭.并且关闭channel后,便不可再想channel中继续存入数据,但是可以继续从channel中读取数据.例子如下:
package main
import "fmt"
func main() {
var ch = make(chan int, )
for i := ; i < ; i++ {
ch <- i
}
close(ch)
//ch <- 11 //panic: runtime error: send on closed channel
for i := range ch {
fmt.Println(i) //输出0 1 2 3 4 5 6 7 8 9
}
}
在线示例:https://www.bytelang.com/o/s/c/XBiMiCoE7dc=
channel阻塞超时处理:
goroutine有时候会进入阻塞情况,那么如何避免由于channel阻塞导致整个程序阻塞的发生那?解决方案:通过select设置超时处理,具体程序如下:
package main import (
"fmt"
"time"
) func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case i := <-c:
fmt.Println(i)
case <-time.After(time.Duration() * time.Second): //设置超时时间为3s,如果channel 3s钟没有响应,一直阻塞,则报告超时,进行超时处理.
fmt.Println("timeout")
o <- true
break
}
}
}()
<-o
}
在线示例:https://www.bytelang.com/o/s/c/6V74LnkRLN0=
golang 并发总结:
并发两种方式:sync.WaitGroup,该方法最大优点是Wait()可以阻塞到队列中的所有任务都执行完才解除阻塞,但是它的缺点是不能够指定并发协程数量.
channel优点:能够利用带缓存的channel指定并发协程goroutine,比较灵活.但是它的缺点是如果使用不当容易造成死锁;并且他还需要自己判定并发goroutine是否执行完.
但是相对而言,channel更加灵活,使用更加方便,同时通过超时处理机制可以很好的避免channel造成的程序死锁,因此利用channel实现程序并发,更加方便,更加易用.
go语言中sync包和channel机制的更多相关文章
- golang语言中sync/atomic包的学习与使用
package main; import ( "sync/atomic" "fmt" "sync" ) //atomic包提供了底层的原子级 ...
- golang语言中bytes包的常用函数,Reader和Buffer的使用
bytes中常用函数的使用: package main; import ( "bytes" "fmt" "unicode" ) //byte ...
- golang语言中os包的学习与使用(文件,目录,进程的操作)
os中一些常用函数的使用: package main; import ( "os" "fmt" "time" "strings&q ...
- go语言中net包tcp socket的使用
一.通过socket我们模拟请求网易 package main; import ( "net" "log" "io/ioutil" &quo ...
- Go语言中Path包用法
// path package main import ( "fmt" "os" "path" "path/filepath&qu ...
- go语言中strings包中的Trim函数的作用是什么
答:Trim函数原型如下: func Trim(s string, cutset string) string 去掉字符串s中首部以及尾部与字符串cutset中每个相匹配的字符,如: s=" ...
- go语言中log包的使用
package main import ( "github.com/robertkrimen/otto" "log" ) func main() { log.P ...
- go语言中strings包常用方法
strings.HasPrefix(s string, prefix string) bool:判断字符串s是否以prefix开头 strings.HasSuffix(s string, suffix ...
- go语言中regexp包中的函数和方法
// regexp.go ------------------------------------------------------------ // 判断在 b 中能否找到正则表达式 patter ...
随机推荐
- IISExpress配置文件
ASP.NET MVC IISExpress配置文件的一个坑 现象: 昨天在处理PBS系统问题的时候意外发现两个js错误(而同样的代码在同事机器上都没有问题),如下图. 图1 图2 图3 原因分析: ...
- linux有用技巧:使用快照制作虚拟机
在日常的学习其中,假设遇到了集群和负载均衡类的实验,须要用到大量的虚拟机,假设一个一个的去创建,显然是很费力和低效的.所以今天交给大家怎样用快照来制作虚拟机.想要多少给你多少^_^.仅仅要内存够用! ...
- Premiere Pro CC问题集,不断更新
1.Premiere Pro CC不好用? 是的.原因如下: 1.1 Adobe公司不注重用户体验,不注重工作流程,导致这款软件的用户体验很差,设计也很烂.对比Adobe公司当年用户体验最好的软件 F ...
- 1.1 什么是LinQ
如今,软件应用环境越来越多样化,软件需要处理的数据量也日渐庞大,数据之间的关系日渐复杂.从而带动了存储技术的不断发展,越来越多的数据存储格式被应用到各种软件中. 通常,针对数据的查询是用简单的字符串文 ...
- 慧都十年大促起幕,Dev、BCG等明星控件6.8折起!
2013慧都十周年大促正式起幕,DevExpress.BCGControlBar.FastReport.TeeChart等精选明星控件Top 10悉数"价"到,还有更多产品惊喜&q ...
- 开始Unity3D参观考察
前言:这个系列的文章纯属对自己学习的整理,非高手之作. 但确实的记载了我作为一个没接触过3D游戏编程的大学生的心路历程.争取每周整理一次吧. 之所以会開始学Unity3D,最基本的原因是由于在快放暑假 ...
- ofbiz学习笔记01--多表关联查询
不管做什么项目,肯定会用到多表关联查询数据,从网络查询得知ofbiz有三种多表关联查询方法 实现一:Screem.xml 中的 section 里,加 <action>, 加 get-re ...
- Yeoman入门之安装及环境配置
Yeoman入门之安装及环境配置 http://blog.csdn.net/panlingfan/article/details/27345037 http://www.nodejs.orgYEOMA ...
- HTML页面规范分解
百度,淘宝,腾讯三大巨头HTML页面规范分解 [兼容html5方案] 百度贴吧,百度图片的实现 <!--[if lt IE 9]> <script> (function() ...
- 监控系统Opserver
监控系统Opserver的配置调试 Stack Exchange开源其监控系统Opserver有一段时间了.之前在项目中用过他们的MiniProfile来分析页面执行效率和帮助新人了解项目,当他们 ...