這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
那些在Google的大牛們開發出了一種稱為Go的牛叉的語言。乍一看,Ruby和Go有點像遠房表親。其實不然,他們那些互為補充的功能卻讓他們成為一對完美組合。 Ruby程式員花時間瞭解一下Go還是非常有好處的,因為Go語言中一些創新之舉還是很不錯的。 對於我來說,Go彌補了C++和Ruby之間空缺的聯絡。特別是當需要實現高響應的伺服器的時候,我通常會選擇C++,但是這樣我就丟失了Ruby的精細之處。雖然我比較偏愛Ruby,可是即便是最近,當需要效能有明顯提高的時候,Ruby還是應付不來。 Go彌補了這個空缺。它提供像Ruby和Python這樣動態語言的感覺的同時,也提供了編譯語言的效能。 Go同時有一些與眾不同的特性,本文會詳細介紹。好了,讓我們好好瞧瞧吧。 |
Mitisky 翻譯於 10 個月 前 3人頂 頂 翻譯的不錯哦! |
Go好在哪? 當寫一個伺服器的時候,一種實現並發的方式是為每個用戶端開一個線程(你可能會覺得是在扯淡,好吧!沒關係,繼續讀下去),特別是有許多用戶端的時候,這種方式是非常糟糕的。較為好的解決方案是選擇非阻塞IO(大家肯定表示贊同吧)。可是,即便都是Unix系的作業系統(諸如Linux,Mac OS X等等),有效地處理非阻塞IO的機制也是各不相同。此外,除了這些紛繁混雜,還有個C語言。我絕不反對嵌入式裝置使用C語言,因為那絕對是速度第一,開發時間第二的。但是,作為一門日常語言,C已經不能滿足我的需求了。 Go提供了令人驚訝的並發基元(primitives),良好的文法,優秀的函數庫和快速的編譯器。它解決我在使用C(某種程度上C++也是)遇到的問題。即使是基礎代碼變得很大的時候,使用Go語言依然很輕鬆。 在這篇文章中,我會依據文檔,快速的回顧一下Go語言的基礎特性。我們的重點在於突出那些讓Go語言與眾不同的創新之舉。 |
Mitisky 翻譯於 10 個月 前 2人頂 頂 翻譯的不錯哦! |
無聊的基礎介紹 Go語言是很容易上手,在基本文法這方面沒玩什麼新花樣。下面是些基本代碼: package main func main() { } 我們從main函數開始。好了,試著輸出個“Hello,world”吧! package main import "fmt" func main() { fmt.Println("Hello, world!")} Go語言中輸入輸出模組被稱作“fmt”,不像Ruby,這個“fmt”是預設不被包含的。所以需要在檔案開始處用“import”聲明引入。“fmt”模組中的Println函數會將你傳入的字串加上一個分行符號一起輸出(類似ruby的puts函數)。注意Go語言中公用方法是以大寫字母開頭的。 下面看一下簡單的迴圈: package main import "fmt" func main() { //the basic for loop for i:=1; i < 100; i++ { fmt.Println(i) }} 對於for迴圈,Go語言和Ruby完全不同。Go語言的for迴圈或多或少有點像C語言。你需要先定義個變數,然後檢查狀態,最後說明在迭代一次結束後需要做什麼事(這個例子是i遞增)。Go語言中的基本迴圈文法只有這一種。幸運的是,這個for迴圈非常靈活。比如說,下面這個死迴圈: for {} 我希望你能查看一些有個for的文檔[http://golang.org/doc/effective_go.html#for]. |
Mitisky 翻譯於 10 個月 前 2人頂 頂 翻譯的不錯哦! |
請注意在我們的上面的for迴圈中,給變數i賦值的時候,我們沒有用“=”,而是使用了“:=”。這兒有個說明差異的例子: package main import "fmt" func main() { //defines the variable a a := 5 fmt.Println(a) //sets a different value to a a = 10 fmt.Println(a) //another way to define a variable var b int b = 15 fmt.Println(b)} 在main函數的開始,在聲明變數a的同時進行了初始化,所以使用“:="。接下來的是簡單的賦值,所以使用“=”。之所以這樣,是因為實際上Go語言是靜態類型語言,不像Ruby這樣的動態類型。因此編譯器必須得知道這個變數在哪聲明和在哪賦值的。最後一部分代碼比較清楚,就是簡單地使用var關鍵字聲明變數,然後進行賦值。 最後,作為和Ruby中數組的一個相似點,在Go語言中的數組也有分區。下面的代碼中有個[]type的類型,這個type意思是著你希望分區返回的類型。但是這樣的做法有點變扭 : package main func main { ///this creates a slice of integers with length 15 mySlice := make([]int, 15)} 我們需要make()函數來獲得一個分區。 如果這樣繼續下去的話,文章就可能成為Go語言文法的的簡明教程。而我更希望將時間花費在一些有意思的新特性上,而不是這樣的一個文法介紹。基本文法可以參照Go語言的文檔,那會介紹得更好。 下面讓我們看看goroutines吧。 |
Mitisky 翻譯於 10 個月 前 2人頂 頂 翻譯的不錯哦! |
Goroutines 寫並發的代碼已經很困難了,寫並發訪問網路的代碼就更加困難了。問題在於傳統的線程不能很好得伸縮,而且線程一旦運行起來,就會很難去控制。Go語言項目組著手解決這個問題,於是乎goroutine就誕生了。 本質上, goroutines是個輕量級的並發機制,通過使用一種稱為channels的構建來進行線程間互動。它們都非常便於使用: package main import "fmt" func wait() { //wait around with a forever loop for { }} func main() { go wait() fmt.Println("We didn't wait because it was called as a goroutine!")} 在上面的代碼中,wait方法是一個死迴圈,但是我們通過go wait()的方式來調用,而非直接的通過wait()來調用。這是告訴Go我們希望以一個goroutine的方式來調用,同時非同步運行。既然這個迴圈是在後台啟動並執行,那樣運行這個程式就不會因為死迴圈而阻塞。 這麼說,Go從語言本身支援並發。也就是,Go語言中有並發基元(primitives)。這樣意義何在呢?僅僅因為不是由某個庫或者模組來實現並發,這好像不是什麼了不起的舉措啊。但是,實際上goroutine從根本上與線程不同。goroutine更加輕量化。還記得在伺服器中,我們不該為每個用戶端建立一個線程吧?但是,使用goroutine,情況就不同了: package main import ( "fmt" "net") //notice that in the arguments, the name of//the variable comes first, then comes the//type of the variable, just like in "var"//declarationsfunc manageClient(conn net.Conn) { conn.Write([]byte("Hi!")) conn.Close() //do something with the client} func main() { //we are creating a server her that listens //on port 1337. Notice that, similar to Ruby, //a method can have two return values (although //in Ruby, this would be an array instead) listener, err := net.Listen("tcp", ":1337") for { //accept a connection connection, _ := listener.Accept() go manageClient(connection) }} 噢,等會!這些代碼似有那麼一小點複雜啊,雖然想法是很簡單。好吧,讓我們一步一步慢慢來 |
Mitisky 翻譯於 10 個月 前 3人頂 頂 翻譯的不錯哦! |
首先,我們來看一下main函數。在main函數一開始調用了net.Listen方法,該方法會返回兩個值,一個是伺服器串連,另一個是錯誤訊息。然後,進入到服務的主迴圈部分,在這兒程式調用server.Accept方法,然後等待請求。該方法調用後,程式會被掛起,直到有有一個用戶端的串連出現。一旦有個串連出現,我們將connection對象傳值到manageClient方法中,由於通過goroutine的方式調用manageClient,所以主程式會繼續等待處理下一個用戶端串連請求。 最後,關於這個manageClient方法要注意一下。首先,注意一下參數表,是變數名在先,類型在後。這樣的格式多少是由Go語言創造者決定的。你可能甚至可能一周后都沒有注意到。 在方法體中,向用戶端寫入“Hi!”資訊,然後關閉通訊端。 好了,就這麼幾行代碼,我們輕鬆完成了一個基礎伺服器。你可以將它改成一個HTTP代理(如果加上緩衝,那就更棒了)。Goroutines支援我們這麼做。事實上goroutine不單單是一個輕量級的線程,因為還有許多與眾不同的機制在背後在起著作用,所以才可以通過如此簡練的代碼的來實現goroutine功能。 |
Mitisky 翻譯於 10 個月 前 2人頂 頂 翻譯的不錯哦! |
Channels 雖然,單純只有Goroutines已經很有作用了,但是如果在channels概念的支援下,那麼Goroutines將更具威力。Channels是一種goroutine之間或者goroutine和主進程之間的通訊機制。讓我們來看個簡單的執行個體。 package main import ( "fmt") var eventChannel chan int = make(chan int) func sayHello() { fmt.Println("Hello, world!") //pass a message through the eventChannel //it doesn't matter *what* we actually send across eventChannel < - 1} func main() { //run a goroutine that says hello go sayHello() //read the eventChannel //this call blocks so it waits until sayHello() //is done <- eventChannel} 程式中有個調用了sayHellothat方法的goroutine,該方法輸出 “Hello, world”訊息。但是,注意那個eventChannel的聲明。本質上,我們聲明了一個整型的channel。我們可以通過這個channel來發送資料,而其他部分可以從這個channel中讀取資料。這就使得channel成為了一種通訊方式。在 sayHello方法中,eventChannel < - 1將整數1加入到eventChannel中,然後在主函數中,我們可以從 eventChannel將資料讀出。 這兒有一點很重要:預設情況下,如果channel中沒有資料的情況下,從channel中讀資料會被阻塞的,一直阻塞到可以從channel中讀到資料。 |
Mitisky 翻譯於 10 個月 前 2人頂 頂 翻譯的不錯哦! |
來的稍微複雜的: package main import ("fmt") var logChannel chan string = make(chan string) func loggingLoop() { for { //wait for a message to arrive msg := < - logChannel //log the msg fmt.Println(msg) }} func main() { go loggingLoop() //do some stuff here logChannel <- "messaged to be logged" //do other stuff here} 這裡,我們完成了一個main的事件輪詢,它會一直處於監聽事件狀態,也就是loggingLoop函數。它從loggChanne中接收到一個訊息後,就會輸到螢幕。這是一個非常普片的設計,特別在事件輪詢中獲得一些狀態。 就這樣,短短几行代碼,我們就完成了一個main函數和goroutines之間的通訊。由於共用記憶體的通訊方式,存在著諸如互斥鎖,競態條件等問題,早已成為了開發人員的噩夢。但是在Go中,channels的概念解決了多數傳統問題。此外,Go的channels是語言的固有部分,而非附加在某個庫中的。 與Ruby相比,Go的goroutines實際上是運行在後台,並且由語言本身實現的(MRI Ruby整個運行在一個單獨的線程中,所以它不能提供一個真實的並行)。此外,雖然Ruby內建線程實現,但是那實在不好使用。事實上,Agent庫嘗試將一些goroutines精妙的地方引入Ruby中去。 |
Mitisky 翻譯於 10 個月 前 2人頂 頂 翻譯的不錯哦! |
告一段落 (暫時) 這篇文章我們已經講了不少東西了,首先介紹了一些非常基礎的文法,然後直接介紹了Go語言的並發機制。 請繼續關注後續的第2部分,那裡我們會接觸一些複雜文法,和其他一些Go語言帶給我們的牛叉特性。 |
Mitisky 翻譯於 10 個月 前 2人頂 頂 翻譯的不錯哦! |
本文中的所有譯文僅用於學習和交流目的,轉載請務必註明文章譯者、出處、和本文連結
我們的翻譯工作遵照 CC 協議,如果我們的工作有侵犯到您的權益,請及時聯絡我們