您好,登錄后才能下訂單哦!
今天給大家介紹一下Goroutine怎么理解。文章的內容小編覺得不錯,現在給大家分享一下,覺得有需要的朋友可以了解一下,希望對大家有所幫助,下面跟著小編的思路一起來閱讀吧。
Go語言里面的并發使用的是Goroutine,Goroutine可以看做一種輕量級的線程,或者叫用戶級線程。與Java的Thread很像,用法很簡單:
go fun(params);
相當于Java的
new Thread(someRunnable).start();
雖然類似,但是Goroutine與Java Thread有著很大的區別。
Java里的Thread使用的是線程模型的一對一模型,每一個用戶線程都對應著一個內核級線程。
上圖有兩個CPU,然后有4個Java thread,每個Java thread其實就是一個內核級線程,由內核級線程調度器進行調度,輪流使用兩個CPU。內核級線程調度器具有絕對的權力,所以把它放到了下面。內核級線程調度器使用公平的算法讓四個線程使用兩個CPU。
Go的Goroutine是用戶級的線程。同樣是4個Goroutine,可能只對應了兩個內核級線程。Goroutine調度器把4個Goroutine分配到兩個內核級線程上,而這兩個內核級線程對CPU的使用由內核線程調度器來分配。
與內核級線程調度器相比,Goroutine的調度器與Goroutine是平等的,所以把它和Goroutine放到了同一個層次。調度器與被調度者權力相同,那被調度者就可以不聽話了。一個Goroutine如果占據了CPU就是不放手,調度器也拿它沒辦法。
同樣是下面一段代碼:
void run() { int a = 1; while(1==1) { a = 1; } }
在Java里,如果起多個這樣的線程,它們可以平等的使用CPU。但是在Go里面,如果起多個這樣的Goroutine,在啟動的內核級線程個數一定情況下(通常與CPU個數相等),那么最先啟動的Goroutine會一直占據CPU,其它的Goroutine會starve,餓死,因為它不能主動放棄CPU,不配合別人工作。說到配合工作,那就需要說一下協程(coroutine,可以當做cooperative routine),協程需要相互合作,互相協助,才能正常工作,所以叫做協程。
協程并不需要一個調度器,它是完全靠互相之間協調來工作的。協程的定義在學術上很抽象,目前實際應用中,協程通常是使用單個內核級線程,用來把異步編程中使用的難懂的callback方式改成看上去像同步編程的樣子。
比如nodejs是異步單線程事件驅動的,在一段代碼中如果有多次異步操作,比如先調用一個支付系統,得到結果后再更新數據庫,那么可能需要嵌套使用callback。pay函數是一個調用支付系統的操作,異步發出請求后就返回,然后等支付完成的事件后觸發第一個回調函數,這個函數是更新數據庫,又是一個異步操作,等這個異步操作完成后,再次觸發返回更新結果的回調函數。 這里只有兩個異步操作,如果多的話,有可能會有很多嵌套。
pay(amount, callback(payamount) { update(payamount, callback(result) { return result; })});
而使用協程,可以看上去像是同步操作
pay(amount){ //異步,立刻返回 //payamount需要操作完成后才能被賦值 payamount = dopay(amount); yeild main;//把控制權返回主routine //dopay事件完成后,主routine會調起這個routine, //繼續執行doupdate result=doupdate(payamount); yeild main; //再次把控制權返回主routine return result; }
(以上都是偽代碼)
把原來的各種嵌套callback改成協程,那么邏輯就會清晰很多。
Goroutine與Coroutine不一樣,開發者并不需要關心Goroutine如何被調起,如何放棄控制權,而是交給Goroutine調度器來管理。開發者不用關心,但是Go語言的編譯器會替你把工作做了,因為Goroutine必須主動交出控制權才能由調度器統一管理。首先我們可以認為寫上面那種死循環而且不調用任何其他函數的Goroutine是沒意義的,如果真在實際應用中寫出這樣的代碼,那開發者不是一個合格的程序員。一個Goroutine總會調用其他函數的,一種調用是開發者自己寫的函數,一種是Go語言提供的API。那編譯器以及這些API就可以做文章了。
比如
void run() { int a = 0; int b = 1; a = b * 2; for(int i = 0; i < 100; i++) { a = func1(a); } }
那么編譯器可能會在調用其他函數的地方偷偷加上幾條語句,比如:
void run() { int a = 0; int b = 1; a = b * 2; for(int i = 0; i < 100; i++) { //進入調度器,或者以一定概率進入調度器 schedule(); a = func1(a); } }
再比如
void run() { socket = new socket(); while(buffer = socker.read()) { deal(buffer); } }
socker.read()是Go語言提供的一個系統函數,那么Go語言可能在這里面加點操作,讀完數據后,進入調度器,讓調度器決定這個Goroutine是否繼續跑。
下面這段Go語言代碼,把內核級線程設成2個,那么主線程會餓死,而在func1里加一個sleep就可以了,這樣func1才有機會放棄控制權。
當然Go語言的調度器要比這復雜的多。Goroutine與協程還是有區別的,實現原理是一樣的,但是Goroutine的目的是為了實現并發,在Go語言里,開發者不能創建內核級線程,只能創建Goroutine,而協程的目的如上面所示,目前比較常見的用途就是上面這個。Go語言適合編寫高并發的應用,因為創建一個Goroutine的代價很低,而且Goroutine切換上下文開銷也很低,與創建內核級線程相比,Goroutine的開銷可能只是幾十分之一甚至幾百分之一,而且它不占內核空間,每個內核級線程都會占很大的內核空間,能創建的線程數最多也就幾千個,而Goroutine可以很輕松的創建上萬個。
Goroutine底層的實現,在Linux上面是用makecontext,swapcontext,getcontext,setcontext這幾個函數實現的,這幾個系統調用可以實現用戶空間線程上下文的保存和切換。
以上就是Goroutine怎么理解的全部內容了,更多與Goroutine怎么理解相關的內容可以搜索億速云之前的文章或者瀏覽下面的文章進行學習哈!相信小編會給大家增添更多知識,希望大家能夠支持一下億速云!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。