golang并发执行的几种方式

背景

主要记录一下工作中和各个文档里关于golang并发开发的实践。golang并发主要用到了

Channel: 使用channel控制子协程,WaitGroup : 使用信号量机制控制子协程,Context: 使用上下文控制子协程。使用这三种机制中的一种或者多种可以达到并发控制很好的效果。关于这三个知识点,https://blog.csdn.net/LINZEYU666/article/details/123020597介绍的比较详细了,这里只介绍几个场景用法。

实践

waitGroup并发执行不同任务

这种写法适合并发处理少量case,并且funcA和funcB的作用是不同任务的时候。

    wg := sync.WaitGroup{}
    var result1 interface{}
    var result2 interface{}
    wg.Add(2)
    go func() {
        defer func() {
            wg.Done()
        }()
        result1 = funcA()
    }()
    go func() {
        defer func() {
            wg.Done()
        }()
        result1 = funcB()
    }()  
    wg.wait() 

waitGroup并发执行相同任务

假设有一批url,需要并发去抓取,这个时候可能只是请求的地址不同,任务的函数是一致的。这时候可以使用for循环的方式去批量执行。使用该方法时候,需要使用线程安全的结构去做数据同步。此外下文的写法最大的弊端是没法做并发度控制,如果请求过多,容易把下游打满,以及启过多协程,浪费资源,只适合小数据集,


import (
    "fmt"
    "io/ioutil"
    "net/http"
    "strings"
    "sync"
)

func main() {
    idList := []string{"x", "xx"}
    wg := sync.WaitGroup{}
    dataMap := sync.Map{} //  sync.Map是线程安全的,如果使用 map去存储返回结果会报错,
                          //  接受返回结果也可以是channel,channel也是线程安全的
    for _, id := range idList {
        wg.Add(1)
        go func(id string) {
            defer func() {
                wg.Done()
            }()
            data := PostData(id)
            dataMap.Store(id, data)
        }(id)
    }
    wg.Wait()
    for _, id := range idList {
        fmt.Println(dataMap.Load(id))
    }
}

func PostData(id string) string {
    url := "http:xx"
    method := "POST"

    payload := strings.NewReader(fmt.Sprintf(`{"id":"%s"}`, id))

    client := &http.Client{}
    req, err := http.NewRequest(method, url, payload)

    if err != nil {
        fmt.Println(err)
        return ""
    }
    req.Header.Add("Content-Type", "application/json")
    res, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
        return ""
    }
    defer res.Body.Close()

    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Println(err)
        return ""
    }
    return string(body)
}

加入channnel,控制并发度

package main

import (
    "fmt"
    "net/http"
    "sync"
)

func main() {
    urls := []string{"http://a.com", "http://b.com", "http://c.com"}

    // 控制并发度为2
    concurrency := 2
    sem := make(chan struct{}, concurrency)

    var wg sync.WaitGroup
    for _, url := range urls {
        wg.Add(1)
        go func(url string) {
            sem <- struct{}{} // 获取信号量
            defer func() {
                <-sem // 释放信号量
                wg.Done()
            }()

            resp, err := http.Get(url)
            if err != nil {
                fmt.Printf("Error fetching %s: %v\n", url, err)
                return
            }
            defer resp.Body.Close()

            fmt.Printf("Fetched %s with status code %d\n", url, resp.StatusCode)
        }(url)
    }

    wg.Wait()
    fmt.Println("All URLs fetched")
}

使用channel进行并发编程

接下来,我们使用一个for循环来遍历URL切片,并为每个URL启动一个goroutine。在每个goroutine中,我们首先从并发度通道中获取一个信号,表示可以开始请求。然后,我们发送一个http GET请求,并将响应结果发送到结果通道中。最后,我们释放一个信号,表示请求已完成。

在主函数中,我们使用另一个for循环从结果通道中读取所有响应结果,并将它们打印到控制台上。

需要注意的是,我们在并发度通道中使用了一个空结构体{},因为我们只需要通道来控制并发度,而不需要在通道中传递任何数据。此外,我们还使用了通道的阻塞特性来控制并发度,因为当通道已满时,任何试图向通道中发送数据的操作都会被阻塞,直到有空间可用为止。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    urls := []string{"http://www.google.com", "http://www.facebook.com", "http://www.apple.com"}

    // 创建一个通道来控制并发度为2
    concurrency := make(chan struct{}, 2)

    // 创建一个通道来接收响应结果
    results := make(chan string, len(urls))

    for _, url := range urls {
        // 启动一个goroutine来请求url
        go func(url string) {
            // 从通道中获取一个信号,表示可以开始请求
            concurrency <- struct{}{}

            // 发送http GET请求
            resp, err := http.Get(url)
            if err != nil {
                results <- fmt.Sprintf("%s -> error: %s", url, err)
            } else {
                results <- fmt.Sprintf("%s -> status: %s", url, resp.Status)
                resp.Body.Close()
            }

            // 释放一个信号,表示请求已完成
            <-concurrency
        }(url)
    }

    // 从结果通道中读取所有响应结果
    for i := 0; i < len(urls); i++ {
        fmt.Println(<-results)
    }
}

使用context,进行并发控制

这种机制下生成的goruntine是树形结构的,有依赖关系。


func getData(ctx context.Context, result chan string, id string) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("running get Data")
            return
        default:
            resultData := PostData(id)
            result <- resultData

        }
    }
}

func main() {
    idList := []string{"xx", "xxx"}
    ctx := context.Background()
    var result = make(chan string, 2)

    go getData(ctx, result, idList[0])
    go getData(ctx, result, idList[1])
    fmt.Println(<-result)
    fmt.Println(<-result)

}

func PostData(id string) string {
    url := "http://xxx"
    method := "POST"

    payload := strings.NewReader(fmt.Sprintf(`{"id":"%s"}`, id))

    client := &http.Client{}
    req, err := http.NewRequest(method, url, payload)

    if err != nil {
        fmt.Println(err)
        return ""
    }
    req.Header.Add("Content-Type", "application/json")
    res, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
        return ""
    }
    defer res.Body.Close()

    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Println(err)
        return ""
    }
    return string(body)
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Golang(又称为Go)是Google公司开发出的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。Go语言具有以下特点:简洁、高效、安全、并发、跨平台等。在学习Golang的入门阶段,你可以了解以下几个方面: 1. Golang的特点:Go语言具有简洁、高效、安全、并发、跨平台等特点,这些特点使得Golang成为一种非常流行的编程语言。 2. Golang的变量作用域:在Golang中,变量的作用域可以分为全局作用域和局部作用域。 3. Golang执行流程的两种方式Golang执行流程可以通过顺序执行和条件执行两种方式来实现。 4. 在Linux上安装Golang语言开发包:要开始学习Golang,你需要在Linux上安装Golang语言开发包。你可以在Golang官方网站上下载适合你的操作系统和架构的Golang安装包,并按照官方文档进行安装。 5. Golang变量的基本使用:在Golang中,你可以使用var关键字声明变量,并使用:=运算符进行变量的初始化和赋值操作。 6. Golang中整数的类型:在Golang中,整数类型可以分为有符号整数和无符号整数,不同的整数类型有不同的取值范围。 7. Golang基本数据类型的默认值:在Golang中,当你声明一个变量但没有进行初始化时,它的默认值将根据其数据类型而定。 8. 基本数据类型转换为String类型:在Golang中,你可以使用strconv包提供的函数将基本数据类型转换为字符串类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值