首頁 後端開發 Golang go語言中goroutine的使用詳解

go語言中goroutine的使用詳解

Nov 25, 2019 pm 02:34 PM
go語言

go語言中goroutine的使用詳解

go中的goroutine是go語言在語言層級支援並發的一種特性。初接觸go的時候對go的goroutine的歡喜至極,實現並發簡到簡直bt的地步。

但是在專案過程中,越來越發現goroutine是一個很容易被大家濫用的東西。 goroutine是一把雙面刃。這裡列舉goroutine所使用的幾宗罪:

##1、goroutine的指標傳遞是不安全的

fun main() {
    request := request.NewRequest() //这里的NewRequest()是传递回一个type Request的指针
    go saveRequestToRedis1(request)
    go saveReuqestToRedis2(request)
     
    select{}
 
}
登入後複製

非常符合邏輯的程式碼:

主routine開一個routine把request傳遞給saveRequestToRedis1,讓它把請求儲存到redis節點1中

同時開另一個routine把request傳遞給saveReuqestToRedis2,讓它把請求儲存到redis節點2中

然後主routine就進入循環(不結束進程)

#問題現在來了,saveRequestToRedis1和saveReuqestToRedis2兩個函數其實不是我寫的,而是團隊另一個人寫的,我對其中的實現一無所知,也不想去仔細看內部的具體實現。但是根據函數名,我想當然地把request指標傳進去。

實際上saveRequestToRedis1和saveRequestToRedis2 是這樣實現的:

func saveRequestToRedis1(request *Request){
     …
     request.ToUsers = []int{1,2,3} //这里是一个赋值操作,修改了request指向的数据结构
     …
    redis.Save(request)
    return
}
登入後複製

這樣有什麼問題? saveRequestToRedis1和saveReuqestToRedis2兩個goroutine修改了同一個共享資料結​​構,但是由於routine的執行是無序的,因此我們無法保證request.ToUsers設定和redis.Save()是一個原子操作,這樣就會出現實際儲存redis的數據錯誤的bug。

好吧,你可以說這個saveRequestToRedis的函數實現的有問題,沒有考慮到會是使用go routine呼叫。請再想一想,這個saveRequestToRedis的具體實作是沒有任何問題的,它不應該考慮上層是怎麼使用它的。

那就是我的goroutine的使用有問題,主routine在開一個routine的時候並沒有確認這個routine裡面的任何一句程式碼有沒有修改了主routine中的資料。對的,主routine確實需要考慮這個情況。

主goroutine在啟用go routine的時候需要閱讀子routine中的每行程式碼來決定是否有修改共享資料? ?這在實際專案開發過程中是多麼降低開發速度的一件事情啊!

go語言使用goroutine是想減輕並發的開發壓力,卻不曾想是在另一方面增加了開發壓力。

上面說的那麼多,就是想得出一個結論:

gorotine的指標傳遞是不安全的! !

如果上一個例子還不夠隱蔽,這裡還有一個例子:

fun (this *Request)SaveRedis() {
    redis1 := redis.NewRedisAddr("xxxxxx")
    redis2 := redis.NewRedisAddr("xxxxxx")
    go this.saveRequestToRedis(redis1)
    go this.saveRequestToRedis(redis2)
     
    select{}
}
登入後複製

很少人會考慮到this指標指向的物件是否會有問題,這裡的this指標傳遞給routine應該說是非常隱密的。

2、goroutine增加了函數的危險係數

這一點其實也是源自於上面一點。上文說,往一個go函數中傳遞指標是不安全的。那麼換個角度想,你怎麼能保證你要呼叫的函數在函數實作內部不會使用go呢?如果不去看函數體內部具體實現,是沒有辦法確定的。

例如我們將上面的典型例子稍微改改

func main() {
    request := request.NewRequest()
    saveRequestToRedis1(request)
    saveRequestToRedis2(request)
    select{}
}
登入後複製

這下我們沒有使用並發,就一定不會出現這問題了吧?追到函數裡面去,傻眼了:

func saveReqeustToRedis1(request *Request) {
           …
            go func() {
          …
          request.ToUsers = []{1,2,3}
         ….
         redis.Save(request)
    }
}
登入後複製

裡面起了一個goroutine,並修改了request指標所指向的物件。這裡就產生了錯誤了。好吧,如果在呼叫函數的時候,不看函數內部的具體實現,這個問題就無法避免。

所以說,從最壞的思考角度出發,每個呼叫函數理論上來說都是不安全的!試想一下,這個呼叫函數如果不是自己開發群組的人寫的,而是使用網路上的第三方開源程式碼...確實無法想像找出這個bug要花費多少時間。

3、goroutine的濫用陷阱

#看一下這個例子:

func main() {
    go saveRequestToRedises(request)
}
 
func saveRequestToRedieses(request *Request) {
    for _, redis := range Redises {
        go redis.saveRequestToRedis(request)
    }
}
 
func saveRequestToRedis(request *Request) {
            ….
            go func() {
                     request.ToUsers = []{1,2,3}
                        …
                        redis.Save(request)
            }
 
}
登入後複製

神奇啊,go無所不在,好像眨眨眼就在哪裡冒出來了。這就是go的濫用,到處都見到go,但是卻不是很明確,哪裡該用go?為什麼用go? goroutine確實會有效率的提升?

c語言的並發比go語言的並發複雜和繁瑣地多,因此我們在使用之前會深思,考慮使用並發獲得的好處和壞處。

處理方法

下面說幾個我處理這些問題的方法:

1、當啟動一個goroutine的時候,如果一個函數必須要傳遞一個指針,但是函數層級很深,在無法保證安全的情況下,傳遞這個指針指向物件的一個克隆,而不是直接傳遞指針

fun main() {
    request := request.NewRequest()
    go saveRequestToRedis1(request.Clone())
    go saveReuqestToRedis2(request.Clone())
     
    select{}
 
}
登入後複製

Clone函數需要另外寫。可以在結構體定義之後簡單跟上這個方法。如:

func (this *Request)Clone(){
    newRequest := NewRequst()
    newRequest.ToUsers = make([]int, len(this.ToUsers))
    copy(newRequest.ToUsers, this.ToUsers)
 
}
登入後複製

其实从效率角度考虑这样确实会产生不必要的Clone的操作,耗费一定内存和CPU。但是在我看来,首先,为了安全性,这个尝试是值得的。

其次,如果项目对效率确实有很高的要求,那么你不妨在开发阶段遵照这个原则使用clone,然后在项目优化阶段,作为一种优化手段,将不必要的Clone操作去掉。这样就能在保证安全的前提下做到最好的优化。

2、什么时候使用go的问题

有两种思维逻辑会想到使用goroutine:

1 业务逻辑需要并发

比如一个服务器,接收请求,阻塞式的方法是一个请求处理完成后,才开始第二个请求的处理。其实在设计的时候我们一定不会这么做,我们会在一开始就已经想到使用并发来处理这个场景,每个请求启动一个goroutine为它服务,这样就达到了并行的效果。这种goroutine直接按照思维的逻辑来使用goroutine

2 性能优化需要并发

一个场景是这样:需要给一批用户发送消息,正常逻辑会使用

for _, user := range users {
    sendMessage(user)
 
}
登入後複製

但是在考虑到性能问题的时候,我们就不会这样做,如果users的个数很大,比如有1000万个用户?我们就没必要将1000万个用户放在一个routine中运行处理,考虑将1000万用户分成1000份,每份开一个goroutine,一个goroutine分发1万个用户,这样在效率上会提升很多。这种是性能优化上对goroutine的需求

按照项目开发的流程角度来看。在项目开发阶段,第一种思路的代码实现会直接影响到后续的开发实现,因此在项目开发阶段应该马上实现。

但是第二种,项目中是由很多小角落是可以使用goroutine进行优化的,但是如果在开发阶段对每个优化策略都考虑到,那一定会直接打乱你的开发思路,会让你的开发周期延长,而且很容易埋下潜在的不安全代码。

因此第二种情况在开发阶段绝不应该直接使用goroutine,而该在项目优化阶段以优化的思路对项目进行重构。

推荐:golang开发栏目

以上是go語言中goroutine的使用詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

Go的爬蟲Colly中Queue線程的問題是什麼? Go的爬蟲Colly中Queue線程的問題是什麼? Apr 02, 2025 pm 02:09 PM

Go爬蟲Colly中的Queue線程問題探討在使用Go語言的Colly爬蟲庫時,開發者常常會遇到關於線程和請求隊列的問題。 �...

Go語言中用於浮點數運算的庫有哪些? Go語言中用於浮點數運算的庫有哪些? Apr 02, 2025 pm 02:06 PM

Go語言中用於浮點數運算的庫介紹在Go語言(也稱為Golang)中,進行浮點數的加減乘除運算時,如何確保精度是�...

在Go語言中使用Redis Stream實現消息隊列時,如何解決user_id類型轉換問題? 在Go語言中使用Redis Stream實現消息隊列時,如何解決user_id類型轉換問題? Apr 02, 2025 pm 04:54 PM

Go語言中使用RedisStream實現消息隊列時類型轉換問題在使用Go語言與Redis...

在 Go 語言中,為什麼使用 Println 和 string() 函數打印字符串會出現不同的效果? 在 Go 語言中,為什麼使用 Println 和 string() 函數打印字符串會出現不同的效果? Apr 02, 2025 pm 02:03 PM

Go語言中字符串打印的區別:使用Println與string()函數的效果差異在Go...

GoLand中自定義結構體標籤不顯示怎麼辦? GoLand中自定義結構體標籤不顯示怎麼辦? Apr 02, 2025 pm 05:09 PM

GoLand中自定義結構體標籤不顯示怎麼辦?在使用GoLand進行Go語言開發時,很多開發者會遇到自定義結構體標籤在�...

Go語言中`var`和`type`關鍵字定義結構體的區別是什麼? Go語言中`var`和`type`關鍵字定義結構體的區別是什麼? Apr 02, 2025 pm 12:57 PM

Go語言中結構體定義的兩種方式:var與type關鍵字的差異Go語言在定義結構體時,經常會看到兩種不同的寫法:一�...

Go語言中哪些庫是由大公司開發或知名的開源項目提供的? Go語言中哪些庫是由大公司開發或知名的開源項目提供的? Apr 02, 2025 pm 04:12 PM

Go語言中哪些庫是大公司開發或知名開源項目?在使用Go語言進行編程時,開發者常常會遇到一些常見的需求,�...

在Go編程中,如何正確管理Mysql和Redis的連接與釋放資源? 在Go編程中,如何正確管理Mysql和Redis的連接與釋放資源? Apr 02, 2025 pm 05:03 PM

Go編程中的資源管理:Mysql和Redis的連接與釋放在學習Go編程過程中,如何正確管理資源,特別是與數據庫和緩存�...

See all articles