1. 程式人生 > >「一聞秒懂」你瞭解goroutine和channel嗎?

「一聞秒懂」你瞭解goroutine和channel嗎?

開源庫「go home」聚焦Go語言技術棧與面試題,以協助Gopher登上更大的舞臺,歡迎go home~

背景介紹

大家都知道程序是作業系統資源分配的基本單位,有獨立的記憶體空間,執行緒可以共享同一個程序的記憶體空間,所以執行緒相對輕量,上下文切換開銷也小。雖然執行緒已經比較輕量了,但還是佔近1M的記憶體,而今天介紹的有“輕量級執行緒”之稱的Goroutine,可以小至幾十K甚至幾K,切換的開銷更小。

除此之外,在傳統Socket程式設計時,需要維護一個執行緒池來為每個Socket收發包分配執行緒,而且需要將CPU與執行緒數建立對應關係,確保每個任務都能被及時分配給CPU,而Go程式可以智慧地將goroutine中的任務分配到CPU。

如何使用

我們現在假設一個場景,你是一家公司老總,每天要花兩小時處理郵件,六小時開會,那麼,程式可以這樣編寫。

func main() {

	time.Sleep(time.Hour * 2) //處理郵件
	time.Sleep(time.Hour * 6) //開會

	fmt.Println("工作完成了")
}

執行一下

果然,要8小時才能完成工作,那麼怎麼簡化工作呢?沒錯,請一個助理小姐姐幫忙,就讓她來處理郵件,這樣你就只需6小時開會就行了。

開始寫程式碼吧,先來定義一個助理函式。

func assistant() {
	time.Sleep(time.Hour * 2)
}

然後在主函式用一條神器的命令go呼叫它,這樣助理的耗時久不再佔用你的時間了。

func main() {

	go assistant()

	time.Sleep(time.Hour * 6) //開會

	fmt.Println("工作完成了")
}

執行一下

真的只花了六個小時就完成工作了。各位看官們,看到沒,這就是協程,只需要go命令加上函式名,就這麼簡單。

但是我知道勤奮的你是不會滿足於現狀的。

匿名函式

另外,既然goroutine支援普通函式,當然也就支援匿名函式。

go func() {
  time.Sleep(time.Hour * 2)
}()

協程間如何通訊

雖然我們可以輕鬆地建立一堆協程,但是不能通訊的協程是沒有靈魂的。假如助理正在幫你處理郵件時,你突然想請她喝奶茶,那是不是要通知她?

那怎麼通知呢?這就引出了大名鼎鼎的channel,漢譯“通道”,顧名思義它的作用就是在協程之間建立通道,一端可以將資料來源源不斷地傳送到通道的另一端。

而宣告方式也非常簡單,只需要make一下。拿下方程式碼為例,它代表初始化一個通道型別變數,並且通道里只能存放string型別的資料。

ch := make(chan string)

初始化完成後,要想與協程函式建立連線,得先把chan變數傳給協程函式。

go assistant(ch)

當然,協程函式要能接收chan才行,我們縱深到函式內部,看看都幹了些什麼。

func assistant(ch chan string) {

	go func() {
		for {
			fmt.Println("看了一封郵件")
			time.Sleep(time.Second * 1)
		}
	}()

	msg := <-ch
	if msg == "喝奶茶去唄" {
		ch <- "好啊"
	}
}

函式內部又起了一個協程專門處理郵件,同時另外一邊等待老闆通知。細心的你應該看出如何取通道資料了,沒錯,只需要在通道變數前加上<-符號就可以將值取出,同樣的,符號加在後面就是往通道塞資料。

ch <- "pingyeaa"
<- ch

如果通道沒有資料,消費端就會一直阻塞,直到有資料為止。當然編譯器是很聰明的,在編譯的時候如果發現沒有地方往通道里塞資料,它就會panic,提示死鎖。

fatal error: all goroutines are asleep - deadlock!

繼續來看程式碼,大致意思就是老闆如果發“喝奶茶去唄”,就返回“好啊”,因為通道里一開始是沒資料的,所以該協程會一直阻塞,直到主函式往通道中寫入了訊息。

現在來看下主函式的實現邏輯,宣告通道和傳入通道變數就不再贅述了,我們只需要等待5秒鐘之後往通道里寫入喝奶茶訊息即可。因為剛才assistant協程接收到訊息後會往ch寫入“好啊”訊息,所以主函式在發完請求之後應該再讀取從助理那邊傳遞來的訊息。

ch := make(chan string)

go assistant(ch)

time.Sleep(time.Second * 5)
ch <- "喝奶茶去唄"

resp := <-ch
fmt.Println(resp)

同樣,主函式的<-ch也會一直阻塞,直到助理回覆訊息。另外有兩點需要注意,第一,如果main函式趕在goroutine之前執行完畢,那麼goroutine也會銷燬;第二,main也是goroutine。

最後,關閉通道,其實通道關閉不是必須的,它與檔案不同,如果沒有goroutine使用到channel,就會自動銷燬,而close的作用是用來通知通道的另一端不再發送訊息了,另一端可以通過<-ch的第二個引數來獲取通道關閉情況。

close(ch)

data, ok := <-ch

通道的多路複用select

剛才的示例中的<-ch只能讀取通道的一條訊息,如果通道里不止一條訊息,該怎麼讀取呢?

應該很多同學跟我一樣想到的是遍歷,沒錯,遍歷確實可以拿到通道資料。

for {
  fmt.Println(<-ch)
}

也可以這麼遍歷。

for d := range ch {
  fmt.Println(d)
}

但是,如果需要同時接收多個通道資料該怎麼辦?迴圈中接收兩個通道變數?

for {
  data, ok := <-ch1
  data, ok := <-ch2
}

這種方式雖然可以取出資料,但是效能較差,官方給我們提供的select關鍵詞就是專門用來解決多通道資料讀取問題的,語法與switch非常相似。select會將多個通道傳來的資料分發到不同的處理邏輯中。

func main() {

	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {
		for {
			select {
			case d := <-ch1:
				fmt.Println("ch1", d)
			case d := <-ch2:
				fmt.Println("ch2", d)
			}
		}
	}()

	ch1 <- 1
	ch1 <- 2
	ch2 <- 2
	ch1 <- 3
}

模擬超時

除此之外,有些情況下我們不希望通道阻塞太久,假設5秒鐘還取不出通道的資料,就超時退出,那我們可以使用time.After方法來實現。time.After會返回一個通道型別,它的作用是傳入一個目標時間(比如5s),我們在5秒後就可以通過通道獲取預設定的超時通知,這樣就達到了定時器的目的。

func main() {

	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {
		for {
			select {
			case d := <-ch1:
				fmt.Println("ch1", d)
			case d := <-ch2:
				fmt.Println("ch2", d)
			case <-time.After(time.Second * 5):
				fmt.Println("接收超時")
			}
		}
	}()

	time.Sleep(time.Second * 6)
}

通道關閉延伸閱讀

已關閉的通道再發送資料會觸發panic

ch := make(chan int)
close(ch)
ch <- 1
panic: send on closed channel

通道設定長度

可以通過make方法設定通道長度,作為緩衝區,通道滿時生產者端會阻塞,通道取空後消費端會阻塞。

ch := make(chan int, 3)

ch <- 1
ch <- 2
ch <- 2
ch <- 2

fmt.Println(len(ch))

已關閉的通道依然可以讀取資料

ch := make(chan int, 3)

ch <- 1
ch <- 2
ch <- 2

close(ch)

for d := range ch {
  fmt.Println(d)
}

感謝大家的觀看,如果覺得文章對你有所幫助,歡迎關注公眾號「平也」,聚焦Go語言與技術原理。

相關推薦

瞭解goroutinechannel

開源庫「go home」聚焦Go語言技術棧與面試題,以協助Gopher登上更大的舞臺,歡迎go home~ 背景介紹 大家都知道程序是作業系統資源分配的基本單位,有獨立的記憶體空間,執行緒可以共享同一個程序的記憶體空間,所以執行緒相對輕量,上下文切換開銷也小。雖然執行緒已經比較輕量了,但還是佔近1M的記憶

雲計算培訓:分鐘公有雲、私有雲、混合雲......

幫助 使用率 雲計算 形式 由於 定義 服務 雲計 托管服務 近幾年隨著雲計算技術的逐漸普及,越來越多的企業開始選擇了部署雲計算方案,當運營賴於數據結構和網絡管理業務時,雲計算的靈活性、易用性、定制性給企業帶來的優勢是毋庸置疑的,但是公有雲、私有雲、混合雲等等到底都是什麽呢

如何搭建一個最簡單的充值系統

數據 一起 表示 存在 除了 社會 index 原因 必須 ? ???閱讀完本文大概需要5分鐘。 目錄 移動支付 微信支付 支付寶支付 充值體系 最基礎的架構 生產環境應用 總結 參考 ? ???一切都是生意。“天下熙熙皆為利來,天下攘攘皆為利往”。不知

kubernetes v1.5.2搭建,部署nginx,tomcat,三臺centos7 叢集,kubernetes工具

安裝vmware 先安裝一個,centos7 minimal模式,安裝完重啟,輸入root與密碼,進入控制檯 ip addr //可以看ip,minimal是沒有安裝ifconfig等等工具的 配置開機聯網 cd /etc/sysconfig/network-script

SAP 助力正泰雲兩網戰略落地

實現智慧能源提供商轉型升級 SAP 日前宣佈,正泰集團股份有限公司(以下簡稱 「正泰」)將採用 SAP S/4HANA、AIN(裝置互聯雲平臺)、SAP Ariba(採購雲)、SAP SuccessFactors(HR雲)等一系列智慧企業套件,以加速集團「一雲兩網」數字化戰略落地,助力實

文讀 AOP | 想要的最全面 AOP 方法探討

前前言 相信大家在入門 AOP 時,常常被繁多的術語、方法和框架繞暈。AOP 好像有點耳熟?Javaseopt 是個什麼?Javassist 又是啥?Dexposed、APT 也是 AOP?本篇將輔助你快速理清概念,掌握 AOP 思想,找到最適合自己業務場景的 AOP 方法。 前言 上

廠商推送

廠商推送 顧名思義,廠商推送指的就是手機硬體廠商提供的系統級別的推送服務。因為她是系統服務,隨手機開機後她就一直存在著,有效地保證了推送通道的高可用性。在網路暢通和推送訊息內容合法的情況下,通過廠商通道推送訊息給該型號手機,不論應用是否存活,都保證訊息推送到位,所以為了拉活、促新、留存,我們絕對不能錯過她

Redis詳解()冰叔帶瞭解Redis

#!/bin/sh # # redis - this script starts and stops the redis-server daemon # # chkconfig: - 85 15 # description: Redis is a persistent key-va

“天使投資、VC、PE” 與“A輪、B輪、C輪融資”的關係

我們經常看到朋友圈裡某某公司獲得了某輪融資,所謂的A輪B輪究竟是個什麼概念呢?今天就跟小夥伴們分享一下A、B、C、D輪融資與天使投資、VC、PE的關係。   天使投資(AI):天使投資所投的是一些非常早期的專案,有些甚至沒有一個完整的產品和商業計劃,或者僅僅只有一個概念。天

分散式架構下的“負載均衡”

在網站創立初期,我們一般都使用單臺機器提供集中式服務,但隨著業務量越來越大,無論效能還是穩定性上都有了更大的挑戰。這時候我們就會想到通過擴容的方式來提供更好的服務。 什麼是負載均衡 我們一般會把多臺機器組成一個叢集對外提供服務。然而,我們的網站對外提供的

篇博文讓瞭解,Python爬蟲庫的代理設定方法!

學爬蟲我們已經瞭解了多種請求庫,如 Requests、Urllib、Selenium 等。我們接下來首先貼近實戰,瞭解一下代理怎麼使用。   下面我們來梳理一下這些庫的代理的設定方法。   1.獲取代理   在做測試之前,我們需要先獲取一個可用代理,搜尋引擎搜尋“代理

163vip郵箱註冊及登陸方法詳解,分鐘

fff vpd 註冊 是什麽 oss pro shadow water tom 高效辦公,缺了郵箱可不行,163vip郵箱的註冊及登陸方法是什麽呢?一分鐘!用吃一片厚切牛舌的時間帶你秒懂!註冊 1、在百度搜索TOMvip郵箱,點擊進入2、點擊屏幕右側的“立即註冊按鈕”3、選

題多解【CodeForces 85D】Sum of Medians(線段樹 / 分塊)

題目連結 題目大意 實現一個setset,支援插入,刪除,求∑a5k+3∑a5k+3。注意,setset中的數在任何時刻都應該是排好序的。 題解 I 首先想到離線處理,每一個

以太坊到底為什麼要轉Casper?

6月3日舉行的2018以太坊技術及應用大會上,V神在題為“Casper與分片技術最新進展”的主題演講中,再次對以太坊的分片技術進行了詳細介紹。那麼,最近V神總說的分片技術和Casper究竟是什麼?以太坊到底為啥一定要從PoW轉換成PoS?與以太坊效能擴充套件需求相矛盾的POW

CPU使用率

目錄 CPU:Cores, and Hyper-Threading  超執行緒(Hyper-Threading ) 多核(multi-cores) CPU使用率計算

!Python字串格式化之format方法詳解

format是字串內嵌的一個方法,用於格式化字串。以大括號`{}`來標明被替換的字串,一定程度上與`%`目的一致。但在某些方面更加的方便 ## 1、基本用法 **1、按照{}的順序依次匹配括號中的值** ```python s = "{} is a {}".format('Tom', 'Boy')

段小程式碼C++右值引用RVO(返回值優化)的誤區

關於C++右值引用的[參考文件](https://en.cppreference.com/w/cpp/language/reference)裡面有明確提到,右值引用可以延長臨時變數的週期。如: ```C std::string&& r3 = s1 + s1; // okay: rvalue r

Java 泛型,瞭解型別擦除

大家可能會有疑問,我為什麼叫做泛型是一個守門者。這其實是我個人的看法而已,我的意思是說泛型沒有其看起來那麼深不可測,它並不神祕與神奇。泛型是 Java 中一個很小巧的概念,但同時也是一個很容易讓人迷惑的知識點,它讓人迷惑的地方在於它的許多表現有點違反直覺。

我們聽過智商情商,但瞭解過財商?-民興商學院

  財商是指一個人在財務方面的智力,即理財的智慧,包括:一是正確認識金錢及金錢規律的能力;二是正確使用金錢及金錢規律的能力。   信用財商是指你擁有多少財富,並不代表你就可以隨便花這些錢,而是代表你有支配這些錢的權力,財富多少意味著調動資源的大小。     信用卡背後的百

一句話讓瞭解NIOIO的異同

下面的解析摘自網友的回答: Channel 通道Buffer 緩衝區Selector 選擇器其中Channel對應以前的流,Buffer不是什麼新東西,Selector是因為nio可以使用非同步的