17. Goroutine 的并发与超时控制

本文探讨了Goroutine的并发控制,包括如何实现按序返回,以及超时控制的重要性。在按序返回中,利用多个channel的顺序接收实现。对于超时控制,提出通过设置超时函数配合goroutine解决可能的阻塞问题。此外,还介绍了使用buffered channel进行并发限制,以限制同时运行的goroutine数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Goroutine 的并发与超时控制

并发

package main

import (
    "fmt"
    "time"
)

func run(task_id, sleeptime int, ch chan string) {
    time.Sleep(time.Duration(sleeptime) * time.Second)
    ch <- fmt.Sprintf("task id %d , sleep %d second", task_id, sleeptime)
    return
}

func main() {
    input := []int{3, 2, 1}
    ch := make(chan string)
    startTime := time.Now()
    fmt.Println("Multirun start")
    for i, sleeptime := range input {
        go run(i, sleeptime, ch)
    }

    for range input {
        fmt.Println(<-ch)
    }

    endTime := time.Now()
    fmt.Printf("Multissh finished. Process time %s. Number of tasks is %d", endTime.Sub(startTime), len(input))
}
Multirun start
task id 2 , sleep 1 second
task id 1 , sleep 2 second
task id 0 , sleep 3 second
Multissh finished. Process time 3s. Number of tasks is 3
Program exited.

按序返回

package main

import (
    "fmt"
    "time"
)

func run(task_id, sleeptime int, ch chan string) {

    time.Sleep(time.Duration(sleeptime) * time.Second)
    ch <- fmt.Sprintf("task id %d , sleep %d second", task_id, sleeptime)
    return
}

func main() {
    input := []int{3, 2, 1}
    chs := make([]chan string, len(input))
    startTime := time.Now()
    fmt.Println("Multirun start")
    for i, sleeptime := range input {
        chs[i] = make(chan string)
        go run(i, sleeptime, chs[i])
    }

    for _, ch := range chs {
        fmt.Println(<-ch)
    }

    endTime := time.Now()
    fmt.Printf("Multissh finished. Process time %s. Number of tasks is %d", endTime.Sub(startTime), len(input))
}
Multirun start
task id 0 , sleep 3 second
task id 1 , sleep 2 second
task id 2 , sleep 1 second
Multissh finished. Process time 3s. Number of tasks is 3
Program exited.

上述代码,实现按序返回的思路在于,使用了多个 channel 发送数据,那么在接收时便可以通过指定接收 channel 的顺序来实现按序返回。


超时控制

上面并发代码,是没有超时控制的,如果某一个 goroutine 由于意外退出,则会导致接收方一直阻塞,从而挂起主程序。

通常我们可以通过 select + time.After 来进行超时检查,例如这样,我们增加一个函数 Run() ,在 Run() 中执行 go run() 。并通过 select + time.After 进行超时判断。

package main

import (
    "fmt"
    "time"
)

func Run(task_id, sleeptime, timeout int, ch chan string) {
    ch_run := make(chan string)
    go run(task_id, sleeptime, ch_run)
    select {
    case re := <-ch_run:
        ch <- re
    case <-time.After(time.Duration(timeout) * time.Second):
        re := fmt.Sprintf("task id %d , timeout", task_id)
        ch <- re
    }
}

func run(task_id, sleeptime int, ch chan string) {

    time.Sleep(time.Duration(sleeptime) * time.Second)
    ch <- fmt.Sprintf("task id %d , sleep %d second", task_id, sleeptime)
    return
}

func main() {
    input := []int{3, 2, 1}
    timeout := 2
    chs := make([]chan string, len(input))
    startTime := time.Now()
    fmt.Println("Multirun start")
    for i, sleeptime := range input {
        chs[i] = make(chan string)
        go Run(i, sleeptime, timeout, chs[i])
    }

    for _, ch := range chs {
        fmt.Println(<-ch)
    }
    endTime := time.Now()
    fmt.Printf("Multissh finished. Process time %s. Number of task is %d", endTime.Sub(startTime), len(input))
}

并发限制

  • buffered channel:缓冲区已满,发送方阻塞;缓冲区为空,接收方阻塞。

  • unbuffered channel:发送方、接收方没有同时准备好,便阻塞。

可以利用 buffered channel 来实现并发限制。

具体来讲,就是设置一个 buffered channel,在开启一个 goroutine 之前,先往该 channel 中发送数据,每个 goroutine 结束之时,从该 channel 中读取值。便可利用 buffered channel 的 size 来限制同时开启的并发数。

package main

import (
    "fmt"
    "time"
)

func Run(task_id, sleeptime, timeout int, ch chan string) {
    ch_run := make(chan string)
    go run(task_id, sleeptime, ch_run)
    select {
    case re := <-ch_run:
        ch <- re
    case <-time.After(time.Duration(timeout) * time.Second):
        re := fmt.Sprintf("task id %d , timeout", task_id)
        ch <- re
    }
}

func run(task_id, sleeptime int, ch chan string) {

    time.Sleep(time.Duration(sleeptime) * time.Second)
    ch <- fmt.Sprintf("task id %d , sleep %d second", task_id, sleeptime)
    return
}

func main() {
    input := []int{3, 2, 1}
    timeout := 2
    chLimit := make(chan bool, 1)
    chs := make([]chan string, len(input))
    limitFunc := func(chLimit chan bool, ch chan string, task_id, sleeptime, timeout int){
        Run(task_id, sleeptime, timeout, ch)
        <-chLimit
    }
    startTime := time.Now()
    fmt.Println("Multirun start")
    for i, sleeptime := range input {
        chs[i] = make(chan string, 1) // buffered channel,用来解决本示例的 dead lock 问题。
        chLimit <- true
        go limitFunc(chLimit, chs[i], i, sleeptime, timeout)
    }

    for _, ch := range chs {
        fmt.Println(<-ch)
    }
    endTime := time.Now()
    fmt.Printf("Multissh finished. Process time %s. Number of task is %d", endTime.Sub(startTime), len(input))
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值