一、golang 語言特性
golang 語言特性主要包括以下幾點:
q 自動垃圾回收
q 更豐富的內置類型
q 函數多返回值
q 錯誤處理
q 匿名函數和閉包
q 類型和接口
q 併發編程
q 反射
q 語言交互性
- 自動垃圾回收
C語言代碼不支持垃圾自動回收,會導致指針存在如下兩個問題:
void foo()
{
char* p = new char[128];
... // 對p指向的內存塊進行賦值
func1(p); // 使用內存指針
delete[] p;
}
1、各種非預期的原因,比如由於開發者的疏忽導致最後的delete語句沒有被調用,都會引發經典而惱人的內存泄露問題。假如該函數被調用得非常頻繁,那麼我們觀察該進程執行時,會發現該進程所佔用的內存會一直瘋長,直至佔用所有系統內存並導致程序崩潰,而如果泄露的是系統資源的話,那麼後果還會更加嚴重,最終很有可能導致系統崩潰。
2、手動管理內存的另外一個問題就是由於指針的到處傳遞而無法確定何時可以釋放該指針所指向的內存塊。假如代碼中某個位置釋放了內存,而另一些地方還在使用指向這塊內存的指針,那麼這些指針就變成了所謂的“野指針”( wild pointer)或者“懸空指針”( dangling pointer),對這些指針進行的任何讀寫操作都會導致不可預料的後果。
由於其傑出的效率, C和C++語言在非常長的時間內都作爲服務端系統的主要開發語言,比如Apache、 Nginx和MySQL等著名的服務器端軟件就是用C和C++開發的。然而,內存和資源管理一直是一個讓人非常抓狂的難題。服務器的崩潰十有八九就是因爲不正確的內存和資源管理導致,更討厭的是這種內存和資源管理問題即使被發現了,也很難定位到具體的錯誤地點,導致無數程序員通宵達旦地調試程序。這個問題在多年裏被不同人用不同的方式來試圖解決,並誕生了一些非常著名的內存檢查工具,比如Rational Purify、 Compuware BoundsChecker和英特爾的Parallel Inspector等。
到目前爲止,內存泄露的最佳解決方案是在語言級別引入自動垃圾回收算法( Garbage Collection,簡稱GC)。所謂垃圾回收,即所有的內存分配動作都會被在運行時記錄,同時任何對該內存的使用也都會被記錄,然後垃圾回收器會對所有已經分配的內存進行跟蹤監測,一旦發現有些內存已經不再被任何人使用,就階段性地回收這些沒人用的內存。當然,因爲需要儘量最小化垃圾回收的性能損耗,以及降低對正常程序執行過程的影響,現實中的垃圾回收算法要比這個複雜得多,比如爲對象增加年齡屬性等,但基本原理都是如此。
自動垃圾回收在C/C++社區一直作爲一柄雙刃劍看待,雖然到C++0x(後命名爲C++11)正式發佈時,這個呼聲頗高的特性總算是被加入了,但按C++之父的說法,由於C++本身過於強大,導致在C++中支持垃圾收集變成了一個困難的工作。假如C++支持垃圾收集,以下的代碼片段在運行時就會是一個嚴峻的考驗:
int* p = new int;
p += 10; // 對指針進行了偏移,因此那塊內存不再被引用
// …… 這裏可能會發生針對這塊int內存的垃圾收集 ……
p -= 10; // 咦,居然又偏移到原來的位置
*p = 10; // 如果有垃圾收集,這裏就無法保證可以正常運行了
對於以上的這個C語言例子,如果使用Go語言實現,我們就完全不用考慮何時需要釋放之前分配的內存的問題,系統會自動幫我們判斷,並在合適的時候(比如CPU相對空閒的時候)進行自動垃圾收集工作。
2、 更豐富的內置類型
golang增加:
數組類型
字符串類型
字典類型(map)
數組切片(slice)
3、 函數多返回值
目前的主流語言中除Python外基本都不支持函數的多返回值功能,例如C語言中如果需要獲取多個返回值,那麼就需要增加傳參參數以便獲取其他返回值,或者使用數組的形式獲取多個返回值。
Go語言革命性地在靜態開發語言陣營中率先提供了多返回值功能。
例如:
func getName()(firstName, middleName, lastName, nickName string){
return "May", "M", "Chen", "Babe"
}
因爲返回值都已經有名字,因此各個返回值也可以用如下方式來在不同的位置進行賦值,從
而提供了極大的靈活性:
func getName()(firstName, middleName, lastName, nickName string){
firstName = "May"
middleName = "M"
lastName = "Chen"
nickName = "Babe"
return
}
並不是每一個返回值都必須賦值,沒有被明確賦值的返回值將保持默認的空值。而函數的調用相比C/C++語言要簡化很多:
fn, mn, ln, nn := getName()
如果開發者只對該函數其中的某幾個返回值感興趣的話,也可以直接用下劃線作爲佔位符來忽略其他不關心的返回值。下面的調用表示調用者只希望接收lastName的值,這樣可以避免聲明完全沒用的變量:
_, _, lastName, _ := getName()
4、錯誤處理
Go語言引入了3個關鍵字用於標準的錯誤處理流程,這3個關鍵字分別爲defer、 panic和recover
5、匿名函數和閉包
在Go語言中,所有的函數也是值類型,可以作爲參數傳遞。 Go語言支持常規的匿名函數和閉包,比如下列代碼就定義了一個名爲f的匿名函數,開發者可以隨意對該匿名函數變量進行傳遞和調用:
f := func(x, y int) int {
return x + y
}
:= 符號代表是先聲明變量再使用
6、類型和接口
Go語言的類型定義非常接近於C語言中的結構( struct),甚至直接沿用了struct關鍵字。相比而言, Go語言並沒有直接沿襲C++和Java的傳統去設計一個超級複雜的類型系統,不支持繼承和重載,而只是支持了最基本的類型組合功能
7、併發編程(重要)
Go語言引入了goroutine概念,它使得併發編程變得非常簡單。通過使用goroutine而不是裸用操作系統的併發機制,以及使用消息傳遞來共享內存而不是使用共享內存來通信, Go語言讓併發編程變得更加輕盈和安全
通過在函數調用前使用關鍵字go,我們即可讓該函數以goroutine方式執行。 goroutine是一種比線程更加輕盈、更省資源的協程。 Go語言通過系統的線程來多路派遣這些函數的執行,使得每個用go關鍵字執行的函數可以運行成爲一個單位協程。當一個協程阻塞的時候,調度器就會自動把其他協程安排到另外的線程中去執行,從而實現了程序無等待並行化運行。而且調度的開銷非常小,一顆CPU調度的規模不下於每秒百萬次,這使得我們能夠創建大量的goroutine,從而可以很輕鬆地編寫高併發程序,達到我們想要的目的。
Go語言實現了CSP(通信順序進程, Communicating Sequential Process)模型來作爲goroutine間的推薦通信方式。在CSP模型中,一個併發系統由若干並行運行的順序進程組成,每個進程不能對其他進程的變量賦值。進程之間只能通過一對通信原語實現協作。 Go語言用channel(通道)這個概念來輕巧地實現了CSP模型。 channel的使用方式比較接近Unix系統中的管道( pipe)概念,可以方便地進行跨goroutine的通信。另外,由於一個進程內創建的所有goroutine運行在同一個內存地址空間中,因此如果不同的goroutine不得不去訪問共享的內存變量,訪問前應該先獲取相應的讀寫鎖。 Go語言標準庫中的sync包提供了完備的讀寫鎖功能
例程如下:paracalc.go
package main
import "fmt"
func sum(values [] int, resultChan chan int) {
sum := 0
for _, value := range values {
sum += value
}
resultChan <- sum // 將計算結果發送到channel中
}
func main() {
values := [] int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
resultChan := make(chan int, 2)
go sum(values[:len(values)/2], resultChan)
go sum(values[len(values)/2:], resultChan)
sum1, sum2 := <-resultChan, <-resultChan // 接收結果
fmt.Println("Result:", sum1, sum2, sum1 + sum2)
}
8、反射
反射( reflection)是在Java語言出現後迅速流行起來的一種概念。通過反射,你可以獲取對象類型的詳細信息,並可動態操作對象。反射是把雙刃劍,功能強大但代碼可讀性並不理想。若非必要,我們並不推薦使用反射。
Go語言的反射實現了反射的大部分功能,但沒有像Java語言那樣內置類型工廠,故而無法做到像Java那樣通過類型字符串創建對象實例。在Java中,你可以讀取配置並根據類型名稱創建對應的類型,這是一種常見的編程手法,但在Go語言中這並不被推薦
9、語言交互性
由於Go語言與C語言之間的天生聯繫, Go語言的設計者們自然不會忽略如何重用現有C模塊的這個問題,這個功能直接被命名爲Cgo。 Cgo既是語言特性,同時也是一個工具的名稱。在Go代碼中,可以按Cgo的特定語法混合編寫C語言代碼,然後Cgo工具可以將這些混合的C代碼提取並生成對於C功能的調用包裝代碼。開發者基本上可以完全忽略這個Go語言和C語言的邊界是如何跨越的。與Java中的JNI不同, Cgo的用法非常簡單,例如:cprint.go
package main
/*
#include <stdio.h>
*/
import "C"
import "unsafe"
func main() {
cstr := C.CString("Hello, world")
C.puts(cstr)
C.free(unsafe.Pointer(cstr))
}