转:http://blog.csdn.net/kjfcpua/article/details/18265475

Go语言并发的设计模式和应用场景

以下设计模式和应用场景来自Google IO上的关于Goroutine的PPT:https://talks.golang.org/2012/concurrency.slide

本文的示例代码在: https://github.com/hit9/Go-patterns-with-channel

生成器

Python中我们可以使用yield关键字来让一个函数成为生成器,在Go中我们可以使用信道来制造生成器(一种lazy load类似的东西)。

当然我们的信道并不是简单的做阻塞主线的功能来使用的哦。

下面是一个制作自增整数生成器的例子,直到主线向信道索要数据,我们才添加数据到信道

func xrange() chan int{ // xrange用来生成自增的整数
var ch chan int = make(chan int) go func() { // 开出一个goroutine
for i := 0; ; i++ {
ch <- i // 直到信道索要数据,才把i添加进信道
}
}() return ch
} func main() { generator := xrange() for i:=0; i < 1000; i++ { // 我们生成1000个自增的整数!
fmt.Println(<-generator)
}
}

这不禁叫我想起了Python中可爱的xrange, 所以给了生成器这个名字!

服务化

比如我们加载一个网站的时候,例如我们登入新浪微博,我们的消息数据应该来自一个独立的服务,这个服务只负责 返回某个用户的新的消息提醒。

如下是一个使用示例:

func get_notification(user string) chan string{
/*
* 此处可以查询数据库获取新消息等等..
*/
notifications := make(chan string) go func() { // 悬挂一个信道出去
notifications <- fmt.Sprintf("Hi %s, welcome to weibo.com!", user)
}() return notifications
} func main() {
jack := get_notification("jack") // 获取jack的消息
joe := get_notification("joe") // 获取joe的消息 // 获取消息的返回
fmt.Println(<-jack)
fmt.Println(<-joe)
}

多路复合

上面的例子都使用一个信道作为返回值,可以把信道的数据合并到一个信道的。 不过这样的话,我们需要按顺序输出我们的返回值(先进先出)。

如下,我们假设要计算很复杂的一个运算 100-x , 分为三路计算, 最后统一在一个信道中取出结果:

func do_stuff(x int) int { // 一个比较耗时的事情,比如计算
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond) //模拟计算
return 100 - x // 假如100-x是一个很费时的计算
} func branch(x int) chan int{ // 每个分支开出一个goroutine做计算并把计算结果流入各自信道
ch := make(chan int)
go func() {
ch <- do_stuff(x)
}()
return ch
} func fanIn(chs... chan int) chan int {
ch := make(chan int) for _, c := range chs {
// 注意此处明确传值
go func(c chan int) {ch <- <- c}(c) // 复合
} return ch
} func main() {
result := fanIn(branch(1), branch(2), branch(3)) for i := 0; i < 3; i++ {
fmt.Println(<-result)
}
}

select监听信道

Go有一个语句叫做select,用于监测各个信道的数据流动。

如下的程序是select的一个使用例子,我们监视三个信道的数据流出并收集数据到一个信道中。

func foo(i int) chan int {
c := make(chan int)
go func () { c <- i }()
return c
} func main() {
c1, c2, c3 := foo(1), foo(2), foo(3) c := make(chan int) go func() { // 开一个goroutine监视各个信道数据输出并收集数据到信道c
for {
select { // 监视c1, c2, c3的流出,并全部流入信道c
case v1 := <- c1: c <- v1
case v2 := <- c2: c <- v2
case v3 := <- c3: c <- v3
}
}
}() // 阻塞主线,取出信道c的数据
for i := 0; i < 3; i++ {
fmt.Println(<-c) // 从打印来看我们的数据输出并不是严格的1,2,3顺序
}
}

有了select, 我们在 多路复合中的示例代码中的函数fanIn还可以这么来写(这样就不用开好几个goroutine来取数据了):

func fanIn(branches ... chan int) chan int {
c := make(chan int) go func() {
for i := 0 ; i < len(branches); i++ { //select会尝试着依次取出各个信道的数据
select {
case v1 := <- branches[i]: c <- v1
}
}
}() return c
}

使用select的时候,有时需要超时处理, 其中的timeout信道相当有趣:

timeout := time.After(1 * time.Second) // timeout 是一个计时信道, 如果达到时间了,就会发一个信号出来

for is_timeout := false; !is_timeout; {
select { // 监视信道c1, c2, c3, timeout信道的数据流出
case v1 := <- c1: fmt.Printf("received %d from c1", v1)
case v2 := <- c2: fmt.Printf("received %d from c2", v2)
case v3 := <- c3: fmt.Printf("received %d from c3", v3)
case <- timeout: is_timeout = true // 超时
}
}

结束标志

Go并发与并行笔记一我们已经讲过信道的一个很重要也很平常的应用,就是使用无缓冲信道来阻塞主线,等待goroutine结束。

这样我们不必再使用timeout。

那么对上面的timeout来结束主线的方案作个更新:

func main() {

    c, quit := make(chan int), make(chan int)

    go func() {
c <- 2 // 添加数据
quit <- 1 // 发送完成信号
} () for is_quit := false; !is_quit; {
select { // 监视信道c的数据流出
case v := <-c: fmt.Printf("received %d from c", v)
case <-quit: is_quit = true // quit信道有输出,关闭for循环
}
}
}

菊花链

简单地来说,数据从一端流入,从另一端流出,看上去好像一个链表,不知道为什么要取这么个尴尬的名字。。

菊花链的英文名字叫做: Daisy-chain, 它的一个应用就是做过滤器,比如我们来筛下100以内的素数(你需要先知道什么是筛法)

程序有详细的注释,不再说明了。

/*
* 利用信道菊花链筛法求某一个整数范围的素数
* 筛法求素数的基本思想是:把从1开始的、某一范围内的正整数从小到大顺序排列,
* 1不是素数,首先把它筛掉。剩下的数中选择最小的数是素数,然后去掉它的倍数。
* 依次类推,直到筛子为空时结束
*/
package main import "fmt" func xrange() chan int{ // 从2开始自增的整数生成器
var ch chan int = make(chan int) go func() { // 开出一个goroutine
for i := 2; ; i++ {
ch <- i // 直到信道索要数据,才把i添加进信道
}
}() return ch
} func filter(in chan int, number int) chan int {
// 输入一个整数队列,筛出是number倍数的, 不是number的倍数的放入输出队列
// in: 输入队列
out := make(chan int) go func() {
for {
i := <- in // 从输入中取一个 if i % number != 0 {
out <- i // 放入输出信道
}
}
}() return out
} func main() {
const max = 100 // 找出100以内的所有素数
nums := xrange() // 初始化一个整数生成器
number := <-nums // 从生成器中抓一个整数(2), 作为初始化整数 for number <= max { // number作为筛子,当筛子超过max的时候结束筛选
fmt.Println(number) // 打印素数, 筛子即一个素数
nums = filter(nums, number) //筛掉number的倍数
number = <- nums // 更新筛子
}
}

随机数生成器

信道可以做生成器使用,作为一个特殊的例子,它还可以用作随机数生成器。如下是一个随机01生成器:

func rand01() chan int {
ch := make(chan int) go func () {
for {
select { //select会尝试执行各个case, 如果都可以执行,那么随机选一个执行
case ch <- 0:
case ch <- 1:
}
}
}() return ch
} func main() {
generator := rand01() //初始化一个01随机生成器 //测试,打印10个随机01
for i := 0; i < 10; i++ {
fmt.Println(<-generator)
}
}

定时器

我们刚才其实已经接触了信道作为定时器, time包里的After会制作一个定时器。

看看我们的定时器吧!

/*
* 利用信道做定时器
*/ package main import (
"fmt"
"time"
) func timer(duration time.Duration) chan bool {
ch := make(chan bool) go func() {
time.Sleep(duration)
ch <- true // 到时间啦!
}() return ch
} func main() {
timeout := timer(time.Second) // 定时1s for {
select {
case <- timeout:
fmt.Println("already 1s!") // 到时间
return //结束程序
}
}
}

TODO

Google的应用场景例子。


本篇主要总结了使用信道, goroutine的一些设计模式。

Go语言并发与并行学习笔记(三)的更多相关文章

  1. Go语言并发与并行学习笔记(一)

    转:http://blog.csdn.net/kjfcpua/article/details/18265441 如果不是我对真正并行的线程的追求,就不会认识到Go有多么的迷人. Go语言从语言层面上就 ...

  2. Go语言并发与并行学习笔记(二)

    转:http://blog.csdn.net/kjfcpua/article/details/18265461 Go语言的并发和并行 不知道你有没有注意到一个现象,还是这段代码,如果我跑在两个goro ...

  3. Go语言学习笔记三: 常量

    Go语言学习笔记三: 常量 定义常量 常量就是在声明后不能再修改的量. const x int = 100 const y string = "abc" const z = &qu ...

  4. 学习笔记(三)--->《Java 8编程官方参考教程(第9版).pdf》:第十章到十二章学习笔记

    回到顶部 注:本文声明事项. 本博文整理者:刘军 本博文出自于: <Java8 编程官方参考教程>一书 声明:1:转载请标注出处.本文不得作为商业活动.若有违本之,则本人不负法律责任.违法 ...

  5. Oracle学习笔记三 SQL命令

    SQL简介 SQL 支持下列类别的命令: 1.数据定义语言(DDL) 2.数据操纵语言(DML) 3.事务控制语言(TCL) 4.数据控制语言(DCL)  

  6. VSTO学习笔记(三) 开发Office 2010 64位COM加载项

    原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(A ...

  7. ES6学习笔记<三> 生成器函数与yield

    为什么要把这个内容拿出来单独做一篇学习笔记? 生成器函数比较重要,相对不是很容易理解,单独做一篇笔记详细聊一聊生成器函数. 标题为什么是生成器函数与yield? 生成器函数类似其他服务器端语音中的接口 ...

  8. MySql学习笔记三

    MySql学习笔记三 4.DML(数据操作语言) 插入:insert 修改:update 删除:delete 4.1.插入语句 语法: insert into 表名 (列名1,列名2,...) val ...

  9. openresty 学习笔记三:连接redis和进行相关操作

    openresty 学习笔记三:连接redis和进行相关操作 openresty 因其非阻塞的调用,令服务器拥有高性能高并发,当涉及到数据库操作时,更应该选择有高速读写速度的redis进行数据处理.避 ...

随机推荐

  1. ACDC

    acdc dcdc电源模块中大功率一般都是开关电源模式的,所以一般输入都是一个较宽的电源范围,体积也相对于变压器要小一些,效率高一些,但是纹波会偏大一些,如何选择就要看电路的需求来选择相应的方案

  2. iOS - Swift NSCalendar 日历

    前言 public class NSCalendar : NSObject, NSCopying, NSSecureCoding NSCalendar 对世界上现存的常用的历法进行了封装,既提供了不同 ...

  3. Linux_服务

    1.服务启动顺序 http://bbs.chinaunix.net/thread-1970916-1-1.html http://bbs.csdn.net/topics/240060477 2.Lin ...

  4. you don't have permission to view it 解决

    the file couldn't be opened because you don't have permission to view it   简单设置下面的选项即可,不要谢我啊! change ...

  5. iOS开发之 几本书

    <object_c 编程之道书> <iOS 7 UI Transition Guide> iOS开发指南:从零基础到App Store上架[国内第一本iOS架构设计图书,涵盖i ...

  6. C++ 11 右值引用

    C++11中引入的一个非常重要的概念就是右值引用.理解右值引用是学习“移动语义”(move semantics)的基础.而要理解右值引用,就必须先区分左值与右值. 注意:左值右值翻译可能有些问题 *L ...

  7. 转:C语言中的static变量和C++静态数据成员(static member)

    转自:C语言中的static变量和C++静态数据成员(static member) C语言中static的变量:1).static局部变量        a.静态局部变量在函数内定义,生存期为整个程序 ...

  8. return 还是 不return

    jQuery.fn.functiona = function() { //return this.each(function() { **** } jQuery(this).css('backgrou ...

  9. 【转】Android设置虚线、圆角、渐变

      Android虚线圆角渐变 有图又真相,先上图再说. 点击效果: 设置虚线: <?xml version="1.0" encoding="utf-8" ...

  10. 添加无线服务wzcsvc服务,Eventlog服务

    <添加eventlog服务.reg> Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentCont ...