参考 《Go微服务实战》

goroutine

先来看一个例子:

package mainimport ("fmt""time"
)func EchoStr(str string) {fmt.Printf("echo : %v \n", str)
}func main() {// 以 go 关键字启动一个协程来打印字符串go EchoStr("Shirley")// 休眠一段时间,让协程有概率执行完毕time.Sleep(1 * time.Second)// main 函数结束时所有协程都会结束,无论是否已经执行完fmt.Println("echo : Sam")
}

运行结果:

echo : Shirley
echo : Sam

解析说明:

goroutine,即协程,是 Go 语言特有的一种轻量级线程,使用关键字 go 启动。goroutine 和系统线程是不一样的,线程是进程的子集,是由进程创建的拥有自己控制流和栈的轻量级实体,而 goroutine 是 Go 语言并法程序的最小单位,运行在操作系统的线程之上,它更为轻量。所有的 Go 语言都是通过 goroutine 来运行,包括 main 函数。一旦 main 函数结束则会结束程序中所有的 goroutine,因此为了保证 EchoStr 函数有概率执行完毕,加了一段休眠时间。

sync.WaitGroup

通过 time.Sleep 休眠来等待 goroutine 执行结束太浪费资源而且不优雅,Go 语言提供了更为优雅且高效的方式来结束 goroutine,示例如下:

package mainimport ("fmt""sync"
)// 定义 WaitGroup
var wg sync.WaitGroupfunc EchoStr(str string) {// goroutine 完成,从 wg 中移除该协程defer wg.Done()fmt.Printf("echo : %v \n", str)
}func main() {// 以 go 关键字启动一个协程来打印字符串go EchoStr("Shirley")// 往 wg 中新加一个 goroutinewg.Add(1)// 等待 wg 中的 goroutine 全部完成wg.Wait()fmt.Println("echo : Sam")
}

运行结果:

echo : Shirley
echo : Sam

channel

channel 用于在 goroutine 之间通信,分为无缓存 channel 和缓存 channel。

无缓存 channel

package mainimport ("fmt""sync"
)// 定义 WaitGroup
var wg sync.WaitGroupfunc InputStr(in chan<- string, str string) {// goroutine 完成,从 wg 中移除该协程defer wg.Done()// 将 str 写入 channelin <- strfmt.Printf("Input : %v \n", str)
}func OutputStr(out <-chan string) {// goroutine 完成,从 wg 中移除该协程defer wg.Done()// 将 channel 中的数据读出来fmt.Printf("Output : %v \n", <-out)
}func main() {// 初始化 channelchannel := make(chan string)// 以 go 关键字启动一个协程来写入字符串go InputStr(channel, "hello")wg.Add(1)// 以 go 关键字启动一个协程来读取字符串go OutputStr(channel)// 往 wg 中新加一个 goroutinewg.Add(1)// 等待 wg 中的 goroutine 全部完成wg.Wait()fmt.Println("done")
}

运行结果:

Output : hello
Input : hello
done

解析说明:

当数据发送到 channel 后,channel 则会处于阻塞状态,等待其他 goroutine 从 channel 中读取数据。同样的,若 channel 中无数据,goroutine 从中读取数据时,则处于阻塞状态,等待其他 goroutine 发送数据到该 channel。将 channel 作为函数参数时可以指定通道方向,也就是指定该通道是接收数据还是发送数据,默认为双向通道。

package mainimport ("fmt""sync""time"
)// 定义 WaitGroup
var wg sync.WaitGroupfunc InputStr(in chan<- string, str string) {// goroutine 完成,从 wg 中移除该协程defer wg.Done()// 将 str 写入 channelin <- strfmt.Printf("Input : %v \n", str)
}func OutputStr(out <-chan string) {// goroutine 完成,从 wg 中移除该协程defer wg.Done()// 将 channel 中的数据读出来fmt.Printf("Output : %v \n", <-out)
}func main() {// 初始化 channelchannel := make(chan string)// 以 go 关键字启动一个协程来读取字符串go OutputStr(channel)// 往 wg 中新加一个 goroutinewg.Add(1)// 使 OutputStr 处于阻塞状态更长时间time.Sleep(time.Second * 2)// 以 go 关键字启动一个协程来写入字符串go InputStr(channel, "hello")wg.Add(1)// 等待 wg 中的 goroutine 全部完成wg.Wait()fmt.Println("done")
}

运行结果:

Input : hello
Output : hello
done

缓存 channel

缓存通道其实是一个队列,可以通过 make 函数初始化时设置 channel 的容量,当 goroutine 往 channel 发送数据时,数据以队列的方式进入 channel,先进先出,即先发送的数据被先读取。同样的,当队列为空,则阻塞读取,直到有其他 goroutine 往 channel 发送数据。示例如下:

package mainimport ("fmt""sync""time"
)// 定义 WaitGroup
var wg sync.WaitGroupfunc InputStr(in chan<- int, i int) {// goroutine 完成,从 wg 中移除该协程defer wg.Done()// 将 i写入 channelin <- ifmt.Printf("Input : %v \n", i)
}func OutputStr(out <-chan int) {// goroutine 完成,从 wg 中移除该协程defer wg.Done()// 将 channel 中的数据读出来fmt.Printf("Output : %v \n", <-out)
}func main() {// 初始化 channelchannel := make(chan int, 3)for i := 0; i < 3; i++ {// 以 go 关键字启动一个协程来读取字符串go OutputStr(channel)// 往 wg 中新加一个 goroutinewg.Add(1)}time.Sleep(time.Second * 2)for i := 0; i < 3; i++ {// 以 go 关键字启动一个协程来写入字符串go InputStr(channel, i)wg.Add(1)}//close(channel)// 等待 wg 中的 goroutine 全部完成wg.Wait()fmt.Println("done")
}

运行结果:

Output : 1
Input : 2
Output : 2
Input : 0
Output : 0
Input : 1
done

特别说明:

close 函数用户关闭 channel 的写入操作,当 channel 被关闭后仍然可以读取数据。注意 main 函数也是由 goroutine 执行的,如果启用 close(channel) 这段代码,则可能 goroutine 未执行完发送数据到 channel 操作就已经把 channel 关闭,其他 goroutine 无法继续发送数据,导致 send error 抛出错误。

select

可以将 select 理解未 switch case ,不过 select 只是用于 channel 的 switch case,它是根据不同的 channel 进入不同的处理代码。示例如下:

package mainimport ("fmt""sync""time"
)// 定义 WaitGroup
var wg sync.WaitGroupfunc InputStr(in chan<- int, i int) {// goroutine 完成,从 wg 中移除该协程defer wg.Done()// 将 i写入 channelin <- ifmt.Printf("Input : %v \n", i)
}func OutputStr(out <-chan int) {// goroutine 完成,从 wg 中移除该协程defer wg.Done()// 将 channel 中的数据读出来fmt.Printf("Output : %v \n", <-out)
}func main() {// 初始化 channelchannel := make(chan int, 3)for i := 1; i <= 3; i++ {// 以 go 关键字启动一个协程来读取字符串go OutputStr(channel)// 往 wg 中新加一个 goroutinewg.Add(1)}time.Sleep(time.Second * 2)for i := 1; i <= 3; i++ {// 以 go 关键字启动一个协程来写入字符串go InputStr(channel, i)wg.Add(1)}for i := 1; i <= 6; i++ {select {case channel <- 1:fmt.Println("send date to channel")case <-channel:fmt.Println("can read from channel")}}//close(channel)// 等待 wg 中的 goroutine 全部完成wg.Wait()fmt.Println("done")
}

运行结果:

Input : 2
Output : 1
send date to channel
can read from channel
send date to channel
can read from channel
send date to channel
send date to channel
Input : 1
Output : 1
Input : 3
Output : 2
done

超时检查

使用 select + time.After() 实现 goroutine 超时检查,示例如下:

package mainimport ("fmt""sync""time"
)// 定义 WaitGroup
var wg sync.WaitGroupfunc InputStr(in chan<- int, i int) {time.Sleep(time.Second * 2)// goroutine 完成,从 wg 中移除该协程defer wg.Done()// 将 i写入 channelin <- ifmt.Printf("Input : %v \n", i)
}func OutputStr(out <-chan int) {// goroutine 完成,从 wg 中移除该协程defer wg.Done()// 将 channel 中的数据读出来fmt.Printf("Output : %v \n", <-out)
}func main() {// 初始化 channelchannel := make(chan int)// 以 go 关键字启动一个协程来写入字符串go InputStr(channel, 17)wg.Add(1)select {case channel <- 1:fmt.Println("send date to channel")case t := <-time.After(time.Second):fmt.Println("send timeout, ", t)}// 以 go 关键字启动一个协程来读取字符串go OutputStr(channel)// 往 wg 中新加一个 goroutinewg.Add(1)select {case <-channel:fmt.Println("can read from channel")case t := <-time.After(time.Second):fmt.Println("read timeout, ", t)}//close(channel)// 等待 wg 中的 goroutine 全部完成wg.Wait()fmt.Println("done")
}

运行结果:

send timeout,  2021-08-19 14:09:45.9280698 +0800 CST m=+1.010278901
Input : 17
Output : 17
read timeout,  2021-08-19 14:09:46.9743473 +0800 CST m=+2.056556401
done

解析说明:

time.After() 设定的等待时间为 1 秒,而 goroutine 的休眠时间为 2 秒,则进入 select case的超时处理逻辑。休眠过后 goroutine 继续执行,因此有输入输出。

sync 包

sync.Mutex 互斥锁

示例如下:

package mainimport ("fmt""sync""time"
)// 定义互斥锁,公共变量
var (m   sync.Mutexnum int
)func Add() {m.Lock()// 模拟一些操作time.Sleep(time.Second)num++m.Unlock()
}func Minus() {m.Lock()// 模拟一些操作time.Sleep(time.Second)num--m.Unlock()
}func EchoRes() int {m.Lock()v := numm.Unlock()return v
}func main() {var wg sync.WaitGroupfor i := 1; i <= 5; i++ {wg.Add(1)go func() {defer wg.Done()Add()fmt.Println("add result", EchoRes())}()wg.Add(1)go func() {defer wg.Done()Minus()fmt.Println("minus result", EchoRes())}()}wg.Wait()fmt.Println("done")
}

运行结果:

minus result -1
minus result -2
add result -1
minus result -2
add result -1
add result 0
minus result -1
add result 0
add result 1
minus result 0
done

解析说明:

一个互斥锁只能锁定一个 goroutine,锁定的是这个锁本身,例如对 num 的自增操作,只有 m 解锁后其他 goroutine 才能执行自减或者自增操作。

sync.RWMutex 多读写锁

示例如下:

package mainimport ("fmt""sync""time"
)// 定义互斥锁,公共变量
var (m   sync.RWMutexnum int
)func Add() {m.Lock()// 模拟一些操作time.Sleep(time.Second)num++m.Unlock()
}func Minus() {m.Lock()// 模拟一些操作time.Sleep(time.Second)num--m.Unlock()
}func EchoRes() int {m.RLock()v := numm.RUnlock()return v
}func main() {var wg sync.WaitGroupfor i := 1; i <= 5; i++ {wg.Add(1)go func() {defer wg.Done()Add()fmt.Println("add result", EchoRes())}()wg.Add(1)go func() {defer wg.Done()Minus()fmt.Println("minus result", EchoRes())}()}wg.Wait()fmt.Println("done")
}

运行结果:

add result 1
minus result 0
minus result -1
add result 0
minus result -1
add result 0
minus result -1
add result 0
add result 1
minus result 0
done

解析说明:

Lock 和 Unlock 是对写的锁,写操作是互斥的;RLock 和 RUnlock 是对读的锁,但读操作不是互斥的。

sync.Once

该方法提供延迟初始化的功能,示例如下:

package mainimport ("fmt""sync"
)// 定义互斥锁,公共变量
var (m   sync.RWMutexnum int
)func Add() {m.Lock()num++m.Unlock()
}func Minus() {m.Lock()num--m.Unlock()fmt.Println("num", num)
}func EchoRes() int {m.RLock()v := numm.RUnlock()return v
}func main() {var (wg   sync.WaitGrouponce sync.Once)for i := 1; i <= 5; i++ {wg.Add(1)go func() {defer wg.Done()Add()once.Do(Minus)}()}wg.Wait()fmt.Println("done")
}

运行结果:

num 0
done

解析说明:

once.Do 在 for 循环中,但只初始化一次:第一层循环,num 自增变成 1 ,调用 once.Do 执行自减,num 变成 0 ,后续循环完毕,只执行了 num 自增操作,自减操作只执行一次。

并发编程 - golang相关推荐

  1. golang并发编程goroutine+channel(一)

    go语言的设计初衷除了在不影响程序性能的情况下减少复杂度,另一个目的是在当今互联网大量运算下,如何让程序的并发性能和代码可读性达到极致.go语言的并发关键词 "go" go dos ...

  2. ​Golang 并发编程指南

    分享 Golang 并发基础库,扩展以及三方库的一些常见问题.使用介绍和技巧,以及对一些并发库的选择和优化探讨. go 原生/扩展库 提倡的原则 不要通过共享内存进行通信;相反,通过通信来共享内存. ...

  3. Golang 并发编程之同步原语

    当提到并发编程.多线程编程时,我们往往都离不开『锁』这一概念,Go 语言作为一个原生支持用户态进程 Goroutine 的语言,也一定会为开发者提供这一功能,锁的主要作用就是保证多个线程或者 Goro ...

  4. Golang系列(三)之并发编程

    版权声明:本文为博主原创文章,未经博主允许不得转载.如需转载请联系本人,并标明作者和出处. https://blog.csdn.net/huwh_/article/details/74858134 本 ...

  5. 《深入学习 Golang》并发编程

    并发编程 并发介绍 goroutine runtime 包 runtime.Gosched() runtime.Goexit() runtime.GOMAXPROCS() channel channe ...

  6. golang sqlx scan 到结构体中_Golang语言并发编程之定时器

    上一章中对于golang的常用关键字说明如下: 1 for 和 range 2 select 3 defer 4 panic 和 recover 5 make 和 new 接下来我们来对golang的 ...

  7. 融云开发漫谈:你是否了解Go语言并发编程的第一要义?

    2007年诞生的Go语言,凭借其近C的执行性能和近解析型语言的开发效率,以及近乎完美的编译速度,席卷全球.Go语言相关书籍也如雨后春笋般涌现,前不久,一本名为<Go语言并发之道>的书籍被翻 ...

  8. 从 bug 中学习:六大开源项目告诉你 go 并发编程的那些坑

    作者:richardyao,腾讯 CSIG 后台开发工程师 并发编程中,go 不仅仅支持传统的通过共享内存的方式来通信,更推崇通过channel来传递消息,这种新的并发编程模型会出现不同于以往的bug ...

  9. 线程互斥与同步 在c#中用mutex类实现线程的互斥_Golang 并发编程与同步原语

    5.1 同步原语与锁 · 浅谈 Go 语言实现原理​draveness.me 当提到并发编程.多线程编程时,我们往往都离不开『锁』这一概念,Go 语言作为一个原生支持用户态进程 Goroutine 的 ...

最新文章

  1. 万物皆可JOJO:这个GAN直接让马斯克不做人啦 | Demo可玩
  2. 实现在Android本地视频播放器开发
  3. Python2/3 list set性能测试
  4. 深度学习利器: TensorFlow系统架构及高性能程序设计
  5. 缘起 Dubbo ,讲讲 Spring XML Schema 扩展机制
  6. #Pragma编译选项
  7. 操作系统:分享10个经常用的cmd命令
  8. 系统集成相关岗位理解
  9. saml2_向SAML响应中添加自定义声明–(如何为WSO2 Identity Server编写自定义声明处理程序)...
  10. 计算机硬件配置组件,配置vcenter server的硬件(默认指windows版本的)
  11. ikbc机械键盘打字出现重复_机械键盘轴体你最爱谁?ikbc新品键盘评测:我爱红轴,不解释!...
  12. 免费报名 | DataFunCon:自然语言处理论坛
  13. 4.3配置自定义情况的Bean实例
  14. 详细解读 | CVPR 2021轻量化目标检测模型MobileDets(附论文下载)
  15. 【YAML】【YAML的实践】【YAML的使用学习记录】
  16. 使用jQuery判断浏览器UA类型
  17. windows phone 7开发日志(正题二,字体)
  18. 计算机专业3分钟演讲能讲什么,面试三分钟演讲稿范文
  19. springboot dubbo的java配置
  20. 一文了解GPU并行计算CUDA

热门文章

  1. (Ryan的Redis系列博客)7.Redis键的生命周期
  2. 军犬舆情热点:诺贝尔颁奖盛典在瑞典举行;多款iPhone禁止销售
  3. EXCEL 同一标签内容求和
  4. 部署的服务器的SQL
  5. HTML5悬浮球源码,js拖拽360桌面悬浮球代码
  6. 在mac 查看、修改文件权限的命令
  7. 常见的Web网站防御攻击方法
  8. 模糊控制系统模糊控制器模块(二)---知识库
  9. 计算机专业男生礼物排行榜,男人内心最想收到的礼物,得票数最多的居然是这十个...
  10. Go 和 Colly笔记