go并发3

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

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

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

生成器

Python中我们可以使用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中可爱的 服务化

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

如下是一个使用示例:

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的一个使用例子,我们监视三个信道的数据流出并收集数据到一个信道中。

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, 我们在 多路复合中的示例代码中的函数

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包里的 看看我们的定时器吧!

/*
 * 利用信道做定时器
 */

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的一些设计模式。

时间: 2024-10-14 02:36:43

go并发3的相关文章

Mysql的锁机制与PHP文件锁处理高并发简单思路

以购买商品举例: ① 从数据库获取库存的数量. ② 检查一下库存的数量是否充足. ③ 库存的数量减去买家购买的数量(以每个用户购买一个为例). ④ 最后完成购买. 仅仅这几行逻辑代码在并发的情况下会出现问题,自己可以想象一下. 这里暂时就不测试了,下面会针对并发的处理给出测试结果. 创建表: CREATE TABLE `warehouse` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `stock` int(11) NOT NULL

Java并发编程:Concurrent锁机制解析

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

VS C++ 并发编程

1.VS2012及以上版本,支持C++11 thread类的并发编程. 相关材料可以参考博客:http://www.cnblogs.com/rangozhang/p/4468754.html 2.但对其之前的版本,可采用以下方式,实现类成员函数创建子线程实现并发. 首先需实现线程类的run函数,故定义了线程类的头文件和其对应的函数实现,具体如图1,2所示: 图1 线程类的头文件 图2 线程类的实现文件 注意到继承的DerivedThread类,只需将并发执行的函数写在其对应的run()函数内即可

快速入门系列--WCF--06并发限流、可靠会话和队列服务

这部分将介绍一些相对深入的知识点,包括通过并发限流来保证服务的可用性,通过可靠会话机制保证会话信息的可靠性,通过队列服务来解耦客户端和服务端,提高系统的可服务数量并可以起到削峰的作用,最后还会对之前的事务知识做一定补充. 对于WCF服务来说,其寄宿在一个资源有限的环境中,为了实现服务性能最大化,需要提高其吞吐量即服务的并发性.然而在不进行流量控制的情况下,并发量过多,会使整个服务由于资源耗尽而崩溃.因此为相对平衡的并发数和系统可用性,需要设计一个闸门(Throttling)控制并发的数量. 由于

多并发时支付如何保持账户余额的一致性?

转载:http://costlend.com/2016/03/14/dispatch-pay-balance-keep-consistence/ 不管是电商,还是O2O业务都会涉及到支付,而且多速情况下流量比较大,尤其是在做活动的时候.一般支付系统主要有充值,扣费,提现,转账等功能,那么在有些业务场景下,尤其是多并发的情况下,我们在做扣费业务操作时该怎样去保持账户余额的一致呢? Java开发人员可能第一个想法就是在调用扣减的DAO的方法上加上一个synchronized关键字,这个解决办法在单节

shell——wait与多进程并发

在脚本里用&后台打开多个子进程,用wait命令可以使这些子进程并行执行. 例1: fun1(){ while true do echo 1 sleep 1 done } fun2(){ while true do echo 2 sleep 1 done } fun1 & fun2 & wait 例2: #!/bin/bash for ((i=0;i<5;i++)) do sleep 3;echo a done #运行需要15秒. #!/bin/bash for ((i=0;i

架构师养成--7.同步类容器和并发类容器

一.同步类容器 同步类容器都是线程安全的,但在某些场景下可能需要加锁来保护复合操作.复合类操作如:迭代(反复访问元素,遍历完容器中的所有元素).跳转(根据指定的顺序找到当前元素的下一个元素).以及条件运算.这些复合操作在多线程并发的修改容器时,可能会表现出意外的行为,最经典的便是ConcurrentModificationException,原因是当容器迭代的过程中,被并发的修改了内容,这是由于早期迭代器设计的时候并没有考虑并发修改的问题. 同步类容器:如古老的Vector/HashTable.

tomcat并发

Tomcat的最大并发数是可以配置的,实际运用中,最大并发数与硬件性能和CPU数量都有很大关系的.更好的硬件,更多的处理器都会使Tomcat支持更多的并发. Tomcat默认的HTTP实现是采用阻塞式的Socket通信,每个请求都需要创建一个线程处理,当一个进程有500个线程在跑的话,那性能已经是很低很低了.Tomcat 默认配置的最大请求数是150,也就是说同时支持150个并发.具体能承载多少并发,需要看硬件的配置,CPU 越多性能越高,分配给JVM的内存越多性能也就越高,但也会加重GC的负担

[原创]java WEB学习笔记93:Hibernate学习之路---Hibernate 缓存介绍,缓存级别,使用二级缓存的情况,二级缓存的架构集合缓存,二级缓存的并发策略,实现步骤,集合缓存,查询缓存,时间戳缓存

本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 -----------------------------------------------------------------------------------------------------------------

大并发大数量中的MYSQL瓶颈与NOSQL介绍

NoSQL在2010年风生水起,大大小小的Web站点在追求高性能高可靠性方面,不由自主都选择了NoSQL技术作为优先考虑的方面.今年伊始,InfoQ中文站有幸邀请到凤凰网的孙立先生,为大家分享他之于NoSQL方面的经验和体会. 非 常荣幸能受邀在InfoQ开辟这样一个关于NoSQL的专栏,InfoQ是我非常尊重的一家技术媒体,同时我也希望借助InfoQ,在国内推动NoSQL 的发展,希望跟我一样有兴趣的朋友加入进来.这次的NoSQL专栏系列将先整体介绍NoSQL,然后介绍如何把NoSQL运用到自