golang并发执行的几种方式小结

 更新时间:2023年08月23日 11:07:03   作者:阿十~  
本文主要介绍了golang并发执行的几种方式小结,主要包括了Channel,WaitGroup ,Context,使用这三种机制中的一种或者多种可以达到并发控制很好的效果,具有一定的参考价值,感兴趣的可以了解一下

背景

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

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

实践

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)
}

到此这篇关于golang并发执行的几种方式小结的文章就介绍到这了,更多相关golang并发执行内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解golang中发送http请求的几种常见情况

    详解golang中发送http请求的几种常见情况

    这篇文章主要介绍了详解golang中发送http请求的几种常见情况,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • Go语言执行cmd命令库的方法实现

    Go语言执行cmd命令库的方法实现

    go语言用来执行一个系统的命令相对python来说还是有点复杂的,执行命令是一个非常常见的需求,本文主要介绍了Go语言执行cmd命令库的方法实现,感兴趣的可以了解一下
    2023-09-09
  • GOLANG使用Context管理关联goroutine的方法

    GOLANG使用Context管理关联goroutine的方法

    这篇文章主要介绍了GOLANG使用Context管理关联goroutine的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-01-01
  • go使用Viper管理配置文件的方法步骤

    go使用Viper管理配置文件的方法步骤

    在项目开发中,需要把一些配置配置提取出来,方便配置和管理,可以使用Viper工具,Viper 是 Go 应用程序的完整配置解决方案,也支持从环境变量中读取,本文给大家介绍了go使用Viper管理配置文件的方法步骤,需要的朋友可以参考下
    2024-07-07
  • Go语法糖之‘...’ 的使用实例详解

    Go语法糖之‘...’ 的使用实例详解

    语法糖(Syntactic sugar),也译为糖衣语法,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。这篇文章主要给大家介绍Go语法糖之‘...’ 的使用,感兴趣的朋友一起看看吧
    2018-10-10
  • Golang 基于flag库实现一个简单命令行工具

    Golang 基于flag库实现一个简单命令行工具

    这篇文章主要介绍了Golang基于flag库实现一个简单命令行工具,Golang标准库中的flag库提供了解析命令行选项的能力,我们可以基于此来开发命令行工具,下文详细介绍。需要的小伙伴可以参考一下
    2022-08-08
  • go语言beego框架分页器操作及接口频率限制示例

    go语言beego框架分页器操作及接口频率限制示例

    这篇文章主要为大家介绍了go语言beego框架分页器操作使用示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04
  • 通过Golang编写一个AES加密解密工具

    通过Golang编写一个AES加密解密工具

    这篇文章主要为大家详细介绍了如何利用Golang制作一个AES加密解密工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2022-05-05
  • GoLang BoltDB数据库详解

    GoLang BoltDB数据库详解

    这篇文章主要介绍了GoLang BoltDB数据库,boltdb是使用Go语言编写的开源的键值对数据库,boltdb存储数据时 key和value都要求是字节数据,此处需要使用到 序列化和反序列化
    2023-02-02
  • pytorch中的transforms.ToTensor和transforms.Normalize的实现

    pytorch中的transforms.ToTensor和transforms.Normalize的实现

    本文主要介绍了pytorch中的transforms.ToTensor和transforms.Normalize的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04

最新评论