golang并發(fā)執(zhí)行的幾種方式小結(jié)
背景
主要記錄一下工作中和各個(gè)文檔里關(guān)于golang并發(fā)開(kāi)發(fā)的實(shí)踐。golang并發(fā)主要用到了
Channel: 使用channel控制子協(xié)程,WaitGroup : 使用信號(hào)量機(jī)制控制子協(xié)程,Context: 使用上下文控制子協(xié)程。使用這三種機(jī)制中的一種或者多種可以達(dá)到并發(fā)控制很好的效果。關(guān)于這三個(gè)知識(shí)點(diǎn),http://www.dbjr.com.cn/jiaoben/296040z3m.htm介紹的比較詳細(xì)了,這里只介紹幾個(gè)場(chǎng)景用法。
實(shí)踐
waitGroup并發(fā)執(zhí)行不同任務(wù)
這種寫(xiě)法適合并發(fā)處理少量case,并且funcA和funcB的作用是不同任務(wù)的時(shí)候。
? ? 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并發(fā)執(zhí)行相同任務(wù)
假設(shè)有一批url,需要并發(fā)去抓取,這個(gè)時(shí)候可能只是請(qǐng)求的地址不同,任務(wù)的函數(shù)是一致的。這時(shí)候可以使用for循環(huán)的方式去批量執(zhí)行。使用該方法時(shí)候,需要使用線程安全的結(jié)構(gòu)去做數(shù)據(jù)同步。此外下文的寫(xiě)法最大的弊端是沒(méi)法做并發(fā)度控制,如果請(qǐng)求過(guò)多,容易把下游打滿(mǎn),以及啟過(guò)多協(xié)程,浪費(fèi)資源,只適合小數(shù)據(jù)集,
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"sync"
)
func main() {
idList := []string{"x", "xx"}
wg := sync.WaitGroup{}
dataMap := sync.Map{} // sync.Map是線程安全的,如果使用 map去存儲(chǔ)返回結(jié)果會(huì)報(bào)錯(cuò),
// 接受返回結(jié)果也可以是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,控制并發(fā)度
package main
import (
"fmt"
"net/http"
"sync"
)
func main() {
urls := []string{"http://a.com", "http://b.com", "http://c.com"}
// 控制并發(fā)度為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{}{} // 獲取信號(hào)量
defer func() {
<-sem // 釋放信號(hào)量
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進(jìn)行并發(fā)編程
接下來(lái),我們使用一個(gè)for循環(huán)來(lái)遍歷URL切片,并為每個(gè)URL啟動(dòng)一個(gè)goroutine。在每個(gè)goroutine中,我們首先從并發(fā)度通道中獲取一個(gè)信號(hào),表示可以開(kāi)始請(qǐng)求。然后,我們發(fā)送一個(gè)http GET請(qǐng)求,并將響應(yīng)結(jié)果發(fā)送到結(jié)果通道中。最后,我們釋放一個(gè)信號(hào),表示請(qǐng)求已完成。
在主函數(shù)中,我們使用另一個(gè)for循環(huán)從結(jié)果通道中讀取所有響應(yīng)結(jié)果,并將它們打印到控制臺(tái)上。
需要注意的是,我們?cè)诓l(fā)度通道中使用了一個(gè)空結(jié)構(gòu)體{},因?yàn)槲覀冎恍枰ǖ纴?lái)控制并發(fā)度,而不需要在通道中傳遞任何數(shù)據(jù)。此外,我們還使用了通道的阻塞特性來(lái)控制并發(fā)度,因?yàn)楫?dāng)通道已滿(mǎn)時(shí),任何試圖向通道中發(fā)送數(shù)據(jù)的操作都會(huì)被阻塞,直到有空間可用為止。
package main
import (
"fmt"
"net/http"
)
func main() {
urls := []string{"http://www.google.com", "http://www.facebook.com", "http://www.apple.com"}
// 創(chuàng)建一個(gè)通道來(lái)控制并發(fā)度為2
concurrency := make(chan struct{}, 2)
// 創(chuàng)建一個(gè)通道來(lái)接收響應(yīng)結(jié)果
results := make(chan string, len(urls))
for _, url := range urls {
// 啟動(dòng)一個(gè)goroutine來(lái)請(qǐng)求url
go func(url string) {
// 從通道中獲取一個(gè)信號(hào),表示可以開(kāi)始請(qǐng)求
concurrency <- struct{}{}
// 發(fā)送http GET請(qǐng)求
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()
}
// 釋放一個(gè)信號(hào),表示請(qǐng)求已完成
<-concurrency
}(url)
}
// 從結(jié)果通道中讀取所有響應(yīng)結(jié)果
for i := 0; i < len(urls); i++ {
fmt.Println(<-results)
}
}使用context,進(jìn)行并發(fā)控制
這種機(jī)制下生成的goruntine是樹(shù)形結(jié)構(gòu)的,有依賴(lài)關(guān)系。
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)
}到此這篇關(guān)于golang并發(fā)執(zhí)行的幾種方式小結(jié)的文章就介紹到這了,更多相關(guān)golang并發(fā)執(zhí)行內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解golang中發(fā)送http請(qǐng)求的幾種常見(jiàn)情況
這篇文章主要介紹了詳解golang中發(fā)送http請(qǐng)求的幾種常見(jiàn)情況,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
Go語(yǔ)言執(zhí)行cmd命令庫(kù)的方法實(shí)現(xiàn)
go語(yǔ)言用來(lái)執(zhí)行一個(gè)系統(tǒng)的命令相對(duì)python來(lái)說(shuō)還是有點(diǎn)復(fù)雜的,執(zhí)行命令是一個(gè)非常常見(jiàn)的需求,本文主要介紹了Go語(yǔ)言執(zhí)行cmd命令庫(kù)的方法實(shí)現(xiàn),感興趣的可以了解一下2023-09-09
GOLANG使用Context管理關(guān)聯(lián)goroutine的方法
這篇文章主要介紹了GOLANG使用Context管理關(guān)聯(lián)goroutine的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01
Golang?基于flag庫(kù)實(shí)現(xiàn)一個(gè)簡(jiǎn)單命令行工具
這篇文章主要介紹了Golang基于flag庫(kù)實(shí)現(xiàn)一個(gè)簡(jiǎn)單命令行工具,Golang標(biāo)準(zhǔn)庫(kù)中的flag庫(kù)提供了解析命令行選項(xiàng)的能力,我們可以基于此來(lái)開(kāi)發(fā)命令行工具,下文詳細(xì)介紹。需要的小伙伴可以參考一下2022-08-08
go語(yǔ)言beego框架分頁(yè)器操作及接口頻率限制示例
這篇文章主要為大家介紹了go語(yǔ)言beego框架分頁(yè)器操作使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
通過(guò)Golang編寫(xiě)一個(gè)AES加密解密工具
這篇文章主要為大家詳細(xì)介紹了如何利用Golang制作一個(gè)AES加密解密工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-05-05
GoLang BoltDB數(shù)據(jù)庫(kù)詳解
這篇文章主要介紹了GoLang BoltDB數(shù)據(jù)庫(kù),boltdb是使用Go語(yǔ)言編寫(xiě)的開(kāi)源的鍵值對(duì)數(shù)據(jù)庫(kù),boltdb存儲(chǔ)數(shù)據(jù)時(shí) key和value都要求是字節(jié)數(shù)據(jù),此處需要使用到 序列化和反序列化2023-02-02
pytorch中的transforms.ToTensor和transforms.Normalize的實(shí)現(xiàn)
本文主要介紹了pytorch中的transforms.ToTensor和transforms.Normalize的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04

