go等待一组协程结束的操作方式

go提供了sync包和channel来解决协程同步和通讯。

方式1:

sync.WaitGroup是等待一组协程结束,sync.WaitGroup只有3个方法,Add()添加一个计数,Done()减去一个计数,Wait()阻塞直到所有任务完成。

package main
import (
	"fmt"
	"sync"
	"time"
)
var wg sync.WaitGroup //定义一个同步等待的组
func task(i int){
	fmt.Println("task...",i)
	//耗时操作任务,网络请求,读取文件
	time.Sleep(time.Second)
	wg.Done() //减去一个计数
}
func main(){
	for i:= 0;i<10;i++{
		wg.Add(1) //添加一个计数
		go task(i)
	}
	wg.Wait() //阻塞直到所有任务完成
	fmt.Println("over")
}

运行结果:

task... 9

task... 4

task... 6

task... 0

task... 7

task... 5

task... 1

task... 2

task... 8

task... 3

over

方式2:

利用缓冲信道channel协程之间通讯,其阻塞等待功能实现等待一组协程结束,不能保证其goroutine按照顺序执行

package main
import (
 "fmt"
)
var ch = make(chan int,10)
func task(i int){
 fmt.Println("task...",i)
 ch <- i
}
func main(){
 for i:= 0;i<10;i++{
  go task(i)
 }
 for i:= 0;i<10;i++{
  <- ch
 }
 fmt.Println("over")
}

运行结果:

task... 9

task... 0

task... 1

task... 2

task... 6

task... 7

task... 3

task... 4

task... 8

task... 5

over

方式3:

利用无缓冲的信道channel协程之间通讯,其阻塞等待功能实现等待一组协程结束,保证了其goroutine按照顺序执行

package main
import (
 "fmt"
 "time"
)
var ch = make(chan int)
func task(i int){
 fmt.Println("task...",i)
 time.Sleep(time.Second)
  <- ch
}
func main(){
 for i:= 0;i<10;i++{
  go task(i)
  ch <- i
 }
 fmt.Println("over")
}

运行结果:

task... 0

task... 1

task... 2

task... 3

task... 4

task... 5

task... 6

task... 7

task... 8

task... 9

over

补充:Go中使用Channel等待所有协程结束

让main方法等待所有协程执行完毕再退出。可能一般思路是设置一个共有变量,然后通过修改这个变量的状态。这是通过共享变量来通信的方式,而go要做的是,通过通信来共享内存。

1. 按顺序执行

每次通信进行成对通信,当main向协程发送一个写channel时,同时也等待协程返回一个读channel。

这两个channel一定是成对的,所以构造一个结构体

type worker struct {
    in chan int
    done chan bool
}

func chanDemo1(){
    var workers [10]worker
    for i := 0; i < 10; i++ {
        workers[i] = createWorker1(i)
    }

    for i := 0; i < 10; i++ {
        workers[i].in <- 'a' + i
        <- workers[i].done
    }

    for i := 0; i < 10; i++ {
        workers[i].in <- 'A' + i
        <- workers[i].done
    }
}

func createWorker1(id int) worker {
    work := worker{
        in: make(chan int),
        done: make(chan bool),
    }
    go func() {
        for {
            fmt.Printf("Work %d receiverd %c\n", id, <- work.in)
            work.done <- true
        }
    }()
    return  work
} 

func main(){
    chanDemo1()
    fmt.Println("over")
}

这个执行结果完全是按照0-9,先小写再大写的顺序

如果这样顺序执行,还要协程干啥

2. 批量处理

type worker struct {
    in chan int
    done chan bool
}

func chanDemo1(){
    var workers [10]worker
    for i := 0; i < 10; i++ {
        workers[i] = createWorker1(i)
    }
    for i := 0; i < 10; i++ {
        workers[i].in <- 'a' + i
    }
    for _, worker  := range workers {
        <- worker.done
    }
    for i := 0; i < 10; i++ {
        workers[i].in <- 'A' + i
    }
    for _, worker  := range workers {
        <- worker.done
    }
}

func createWorker1(id int) worker {
    work := worker{
        in: make(chan int),
        done: make(chan bool),
    }
    go func() {
        for {
            fmt.Printf("Work %d receiverd %c\n", id, <- work.in)
            work.done <- true
        }
    }()
    return  work
}

这样的话,先打印小写,再打印大写,但是大小写时顺序不固定

3. 完全随机

func chanDemo1(){
    var workers [10]worker
    for i := 0; i < 10; i++ {
        workers[i] = createWorker1(i)
    }
    for i := 0; i < 10; i++ {
        workers[i].in <- 'a' + i
    }

    for i := 0; i < 10; i++ {
        workers[i].in <- 'A' + i
    }
    for _, worker  := range workers {
        <- worker.done
        <- worker.done
    }
}

func createWorker1(id int) worker {
    work := worker{
        in: make(chan int),
        done: make(chan bool),
    }
    go func() {
        for {
            fmt.Printf("Work %d receiverd %c\n", id, <- work.in)

            // 再开一个协程
            go func() { work.done <- true}()
        }
    }()
    return  work
}

这种方式就是完全随机了

使用channel进行树的遍历

func (node *Node) TraverseFunc(f func(*Node)){
    if node == nil{
        return
    }
    node.Left.TraverseFunc(f)
    f(node)
    node.Right.TraverseFunc(f)
}

func (node *Node) TraverseWithChannel() chan *Node{
    out := make(chan *Node)
    go func() {
        node.TraverseFunc(func(node *Node) {
            out <- node
        })
        close(out)
    }()
    return out
}

func main(){
    var root Node
    root = Node{Value:3}
    root.Left = &Node{}
    root.Right = &Node{5,nil,nil}
    root.Right.Left = new(Node)
    root.Left.Right =&Node{6,nil,nil}
    root.Traverse()

    c:=root.TraverseWithChannel()
    maxNode := 0
    for node := range c{
        if node.Value > maxNode{
            maxNode = node.Value
        }
    }
    fmt.Println("max node value:", maxNode)
 

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • go build 通过文件名后缀实现不同平台的条件编译操作

    go build 可以通过标签或者文件名的后缀来提供条件编译,这里说下通过文件名的后缀来提供条件编译 文件命名约定可以在go build 包里找到详细的说明,简单来说,就是源文件包含后缀:_$GOOS.go,那么这个源文件只会在这个平台下编译,_$GOARCH.go也是如此. 这两个后缀可以结合在一起使用,但是要注意顺序:_$GOOS_$GOARCH.go,不能反过来用:_$GOARCH_$GOOS.go 例如下面截图,这些文件定义了对应不同平台下是否需要编译. 补充:Golang: 条件和循环

  • Go并发:使用sync.WaitGroup实现协程同步方式

    经常看到有人会问如何等待主协程中创建的协程执行完毕之后再结束主协程,例如如下代码: package main import ( "fmt" ) func main() { go func() { fmt.Println("Goroutine 1") }() go func() { fmt.Println("Goroutine 2") }() } 执行以上代码很可能看不到输出,因为有可能这两个协程还没得到执行主协程已经结束了,而主协程结束时会结束所

  • golang协程池模拟实现群发邮件功能

    比如批量群发邮件的功能 因为发送邮件是个比较耗时的操作, 如果是传统的一个个执行 , 总体耗时比较长 可以使用golang实现一个协程池 , 并行发送邮件 pool包下的pool.go文件 package pool import "log" //具体任务,可以传参可以自定义操作 type Task struct { Args interface{} Do func(interface{})error } //协程的个数 var Nums int //任务通道 var JobChanne

  • SpringBoot整合MongoDB实现文件上传下载删除

    本文主要内容 MongoDB基础操作命令示例练习 MongoDB居于GridFSTemplate的文件上传.下载.删除等操作(工作重点使用) 1. 基础命令 创建的数据库名称:horse,创建的集合名称:blog # 创建数据库 use horse # 删除当前数据库[horse] db.dropDatebase() # 查看所有数据库 show dbs # 设置用户的角色和权限 db.createUser({user:"horse",pwd:"mongo123",

  • Go递归修改文件名的实例代码

    在生活中我们往往有这样的需求,就是从网上找的文件资源经常包含了一些无用信息,而且在它的子目录下也同样存在,如果我们手动一个个的修改的话会特别麻烦,也特别耗时,我们可以考虑把这件事交给计算机来做. 如下图,我的桌面有一个名为dir[我爱你]的目录,而且这个目录下的子目录和文件都包含[我爱你],这个[我爱你]对于我们来说是完全无用的,我们可以用Go的文件操作库来递归对文件进行重命名,把不需要的名字替换为空 示例代码: package main import ( "bufio" "

  • Go 如何批量修改文件名

    工作中遇到了批量修改文件名的情况,为此写了个小程序,以供日后方便查看. 用法: renamedirfiles.exe -d "E:\shared\图片素材\ps123_20121120_01\背景图片打包下载" -p "bg%d" 代码如下: package main import ( "flag" "fmt" "os" "path/filepath" ) func main() { /

  • go等待一组协程结束的操作方式

    go提供了sync包和channel来解决协程同步和通讯. 方式1: sync.WaitGroup是等待一组协程结束,sync.WaitGroup只有3个方法,Add()添加一个计数,Done()减去一个计数,Wait()阻塞直到所有任务完成. package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup //定义一个同步等待的组 func task(i int){ fmt.P

  • Golang控制通道实现协程等待详解

    目录 前言 方法一-睡眠等待 方法二-通道 什么是通道 通道的特性 什么是非缓冲通道 什么是缓冲通道 通道的简单使用 非缓冲通道 缓冲通道 小心死锁 使用通道实现协程等待 前言 上一次简单了解了协程的工作原理 前文链接 最后提到了几个使用协程时会遇到的问题,其中一个就是主线程不会等待子线程结束,在这里记录两种比较简单的方法,并借此熟悉下通道的概念. 方法一-睡眠等待 简单暴力的解决方案,在创建了子协程之后,主协程等待一段时间再结束. func goroutineTest(i int) { fmt

  • Python 异步如何使用等待有时间限制协程

    目录 正文 1. 什么是 Asyncio wait_for() 2. 如何使用 Asyncio wait_for() 3. 带有超时的 Asyncio wait_for() 示例 正文 我们可以使用 asyncio.wait_for() 函数等待 asyncio 任务或协程超时完成.如果在任务完成之前超时已过,任务将被取消. 1. 什么是 Asyncio wait_for() asyncio.wait_for() 函数允许调用者等待 asyncio 任务或协程超时完成.如果没有指定超时,wait

  • Go 并发实现协程同步的多种解决方法

    go 简洁的并发 多核处理器越来越普及.有没有一种简单的办法,能够让我们写的软件释放多核的威力?是有的.随着Golang, Erlang, Scala等为并发设计的程序语言的兴起,新的并发模式逐渐清晰.正如过程式编程和面向对象一样,一个好的编程模式有一个极其简洁的内核,还有在此之上丰富的外延.可以解决现实世界中各种各样的问题.本文以GO语言为例,解释其中内核.外延. 前言 Java 中有一系列的线程同步的方法,go 里面有 goroutine(协程),先看下下面的代码执行的结果是什么呢? pac

  • 一文详解go同步协程的必备工具WaitGroup

    目录 1. 简介 2. 基本使用 2.1 定义 2.2 使用方式 2.3 使用例子 3.实现原理 3.1 设计初衷 3.2 基本原理 3.3 代码实现 3.3.1 Add方法 3.3.2 Done方法实现 3.3.3 Wait方法实现 3.4 实现补充 4.使用注意事项 4.1 Add方法和Done方法需要成对出现 4.2 在所有任务都已经添加之后,才调用Wait方法进行等待 5. WaitGroup常见使用场景 总结 1. 简介 本文将介绍 Go 语言中的 WaitGroup 并发原语,包括

  • Python并发编程协程(Coroutine)之Gevent详解

    Gevent官网文档地址:http://www.gevent.org/contents.html 基本概念 我们通常所说的协程Coroutine其实是corporateroutine的缩写,直接翻译为协同的例程,一般我们都简称为协程. 在linux系统中,线程就是轻量级的进程,而我们通常也把协程称为轻量级的线程即微线程. 进程和协程 下面对比一下进程和协程的相同点和不同点: 相同点: 我们都可以把他们看做是一种执行流,执行流可以挂起,并且后面可以在你挂起的地方恢复执行,这实际上都可以看做是con

  • Python中协程用法代码详解

    本文研究的主要是python中协程的相关问题,具体介绍如下. Num01–>协程的定义 协程,又称微线程,纤程.英文名Coroutine. 首先我们得知道协程是啥?协程其实可以认为是比线程更小的执行单元. 为啥说他是一个执行单元,因为他自带CPU上下文.这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程. 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的. Num02–>协程和线程的差异 那么这个过程看起来和线程差不多.其实不然, 线程切换从系统层面远不止保存和恢复 CP

  • python asyncio 协程库的使用

    asyncio 是 python 力推多年的携程库,与其 线程库 相得益彰,更轻量,并且协程可以访问同一进程中的变量,不需要进程间通信来传递数据,所以使用起来非常顺手. asyncio 官方文档写的非常简练和有效,半小时内可以学习和测试完,下面为我的一段 HelloWrold,感觉可以更快速的帮你认识 协程 . 定义协程 import asyncio import time async def say_after(delay, what): await asyncio.sleep(delay)

  • 深入理解python协程

    一.什么是协程 协程拥有自己的寄存器和栈.协程调度切换的时候,将寄存器上下文和栈都保存到其他地方,在切换回来的时候,恢复到先前保存的寄存器上下文和栈,因此:协程能保留上一次调用状态,每次过程重入时,就相当于进入上一次调用的状态. 协程的好处: 1.无需线程上下文切换的开销(还是单线程) 2.无需原子操作(一个线程改一个变量,改一个变量的过程就可以称为原子操作)的锁定和同步的开销 3.方便切换控制流,简化编程模型 4.高并发+高扩展+低成本:一个cpu支持上万的协程都没有问题,适合用于高并发处理

随机推荐