Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
下文章來源于大愚Talk ,作者大愚Talk
對于 Golang 語言應用層面的知識,先講如何正確的使用,然后再講它的實現。
Don't communicate by sharing memory, share memory by communicating.
相信寫過 Go 的同學都知道這句名言,可以說 channel 就是后邊這句話的具體實現。我們來看一下到底 channel 是什么?
channel 是一個類型安全的隊列(循環隊列),能夠控制 groutine 在它上面讀寫消息的行為,比如:阻塞某個 groutine ,或者喚醒某個 groutine。
不同的 groutine 可以通過 channel 交換任意的資源,由于 channel 能夠控制 groutine 的行為,所以 CSP 模型才能在 Golang 中順利實現,它確保了不同 groutine 之間的數據同步機制。
上面的話是不是聽起來非常的不舒服?
好吧,簡單說人話就是,channel 是用來在 不同的 的 goroutine 中交換數據的。一定要注意這里 不同的 三個字。千萬不要把 channel 拿來在不同函數(同一個 goroutine 中)間交換數據。
知道了定義,我們來看具體如何使用。
如何定義一個 channel 類型呢?
var ch1 chan int // 定義了一個 int 類型的 channel,沒有初始化,是 nil
ch2 := make(chan int) // 定義+初始化了一個無緩沖的 int 類型 channel
ch3 := make(chan int) // 定義+初始化了一個有緩沖的 int 類型 channel
上面的定義方法我們都是定義的雙向通道,對應的還有單向通道,但是單向通道我們一般只是做為函數參數來進行一些限制,并不會在定義、初始化時就搞一個單向通道出來。因為你定義一個單向通道沒有任何實際價值,通道的存在本來就是用來交換數據的,單向通道只能滿足發或者收。
下面我們一起來看一下具體的使用,以及使用中注意的一些點。
不管是有緩沖的通道還是無緩沖的通道都是用來交換數據的,既然是交換數據,無非就是寫入、讀取。我們先從發送開始。
ch := make(chan int)
defer close(ch)
//ch<-5 // 位置一
go func(ch chan int) {
num := <-ch
fmt.Println(num)
}(ch)
// ch<-5 // 位置二
如果我們打開 位置一 的注釋,程序是無法獲得預期執行的,由于該 channel 是無緩沖的,位置一的代碼會陷入阻塞,下一行的 goroutine 根本沒有機會執行。整個代碼會陷入死鎖。
正確的操作是,打開 位置二 的注釋,因為上一行 goroutine 先行啟動,他是一個獨立的協程,不會阻塞主 groutine 的執行。但它內部會阻塞在 num :=<-ch 這行代碼,直到主協程執行完 ch<-5 ,才會執行打印。所以這里也有一個非常重要的問題,主協程如果不等待子協程執行完就退出的話,會看不到執行結果。
這里先提一點,無緩沖的 channel 并不會用到內部結構體的 buf ,這部分具體會在源碼部分講解他們的數據存取、交換的方式。
ch := make(chan int, 1) // 注意這里
defer close(ch)
//ch<-5 // 位置一
go func(ch chan int) {
num := <-ch
fmt.Println(num)
}(ch)
// ch<-5 // 位置二
代碼基本沒有改變,唯一的區別是 make 函數傳入了第二個參數,這個值的含義是緩沖的大小。那么此時 位置一 與 位置二 都能夠正常執行嗎?
答案是肯定的,此時的代碼,無論是那個位置,打開注釋后都能夠正常執行。原因就在于由于 channel 有了緩存區域,位置一 寫入數據不會造成主協程的阻塞,那么下一行代碼的子協程就可以正常啟動,并直接將位置一寫入 buf 的數據讀取出來打印。
對于 位置二 ,由于子協程先啟動,但是會被阻塞在 num :=<-ch 這一行,因為此時 buf 中沒有任何內容可讀取(下期源碼分析我們可以看代碼實現),直到位置二執行完,喚醒子協程。
發送需要注意幾個問題:
有寫入,必然后讀取。
還是上面的代碼, num :=<-ch 就是從 channel 讀取數據。對于讀取就不按照有緩沖與無緩沖來講解了,它們的主要問題是什么時候阻塞。通過上面寫的例子自己再想想即可。
這里說下讀取的兩種形式。
形式一
multi-valued assignment
v, ok := <-ch
ok 是一個 bool 類型,可以通過它來判斷 channel 是否已經關閉,如果關閉該值為 true ,此時 v 接收到的是 channel 類型的零值。比如:channel 是傳遞的 int, 那么 v 就是 0 ;如果是結構體,那么 v 就是結構體內部對應字段的零值。
形式二
v := <-ch
該方式對于關閉的 channel 無法掌控,我們示例中就是該種方式。
接收需要注意幾個問題:
對于 channel 的關閉,在什么地方去關閉呢?因為上面也講到向 closed 的 channel 寫或者繼續 close 都會導致 panic問題。
一般的建議是誰寫入,誰負責關閉。如果涉及到多個寫入的協程、多個讀取的協程?又該如何關閉?總的來說就是加入一個標記避免重復關閉。不過真的不建議搞的太復雜,否則后續維護代碼會瘋掉。
關閉需要注意幾個問題:
我們常常會用 for-range 來讀取 channel的數據。
ch := make(chan int, 1)
go func(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}(ch)
for val := range ch {
fmt.Println(val)
}
該語句的一個特色是如果 channel 已經被關閉,它還是會繼續執行,直到所有值被取完,然后退出執行。而如果通道沒有關閉,但是channel沒有可讀取的數據,它則會阻塞在 range 這句位置,直到被喚醒。但是如果 channel 是 nil,那么同樣符合我們上面說的的原則,讀取會被阻塞,也就是會一直阻塞在 range位置。
select 是跟 channel 關系最親密的語句,它是被專門設計出來處理通道的,因為每個 case 后面跟的都是通道表達式,可以是讀,也可以是寫。
ch := make(chan int)
q := make(chan int)
go func(ch, q chan int) {
for i := 0; i < 10; i++ {
num := <-ch
fmt.Println(num)
}
q <- 1
}(ch, q)
fibonacci := func(ch, q chan int) {
x, y := 0, 1
for {
select {
case ch <- x: // 寫入
x, y = y, x+y
break // 你覺得是否會影響 for 語句的循環?
case <-q: // 讀取
fmt.Println("quit")
return
}
}
}
fibonacci(ch, q)
上面的代碼是利用 channel 實現的一個斐波拉契數列。select 還可以有 default 語句,該語句會在其它 case 都被阻塞的情況下執行。
關注的問題
本文內容很簡單易懂,希望大家徹底掌握了 channel 的使用。一切源碼的研究都是為了更好的使用,后面的文章將開始研究 channel 的源碼實現。
本文幾個重要問題再次總結下,也是經常面試的常考點。
參考資料
o (計算機編程語言)
Go(又稱 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 開發的一種靜態強類型、編譯型語言。Go 語言語法與 C 相近,但功能上有:內存安全,GC(垃圾回收),結構形態及 CSP-style 并發計算。
Go(又稱Golang)是Google開發的一種靜態強類型、編譯型、并發型,并具有垃圾回收功能的編程語言。
Python
Python是一種計算機程序設計語言。是一種面向對象的動態類型語言,最初被設計用于編寫自動化腳本(shell),隨著版本的不斷更新和語言新功能的添加,越來越多被用于獨立的、大型項目的開發。
Python是一種解釋型腳本語言,可以應用于以下領域:
1、Web 和 Internet開發
2、科學計算和統計
3、人工智能
4、教育
5、桌面界面開發
6、軟件開發
7、后端開發
優點
簡單:Python是一種代表簡單主義思想的語言。閱讀一個良好的Python程序就感覺像是在讀英語一樣。它使你能夠專注于解決問題而不是去搞明白語言本身。
易學:Python極其容易上手,因為Python有極其簡單的說明文檔 。
速度快:Python 的底層是用 C 語言寫的,很多標準庫和第三方庫也都是用 C 寫的,運行速度非常快。
免費、開源:Python是FLOSS(自由/開放源碼軟件)之一。使用者可以自由地發布這個軟件的拷貝、閱讀它的源代碼、對它做改動、把它的一部分用于新的自由軟件中。FLOSS是基于一個團體分享知識的概念。
高層語言:用Python語言編寫程序的時候無需考慮諸如如何管理你的程序使用的內存一類的底層細節。
可移植性:由于它的開源本質,Python已經被移植在許多平臺上(經過改動使它能夠工作在不同平臺上)。這些平臺包括Linux、Windows、FreeBSD、Macintosh、Solaris、OS/2、Amiga、AROS、AS/400、BeOS、OS/390、z/OS、Palm OS、QNX、VMS、Psion、Acom RISC OS、VxWorks、PlayStation、Sharp Zaurus、Windows CE、PocketPC、Symbian以及Google基于linux開發的android平臺。
解釋性:一個用編譯性語言比如C或C++寫的程序可以從源文件(即C或C++語言)轉換到一個你的計算機使用的語言(二進制代碼,即0和1)。這個過程通過編譯器和不同的標記、選項完成。
運行程序的時候,連接/轉載器軟件把你的程序從硬盤復制到內存中并且運行。而Python語言寫的程序不需要編譯成二進制代碼。你可以直接從源代碼運行 程序。
在計算機內部,Python解釋器把源代碼轉換成稱為字節碼的中間形式,然后再把它翻譯成計算機使用的機器語言并運行。這使得使用Python更加簡單。也使得Python程序更加易于移植。
面向對象:Python既支持面向過程的編程也支持面向對象的編程。在“面向過程”的語言中,程序是由過程或僅僅是可重用代碼的函數構建起來的。在“面向對象”的語言中,程序是由數據和功能組合而成的對象構建起來的。
可擴展性:如果需要一段關鍵代碼運行得更快或者希望某些算法不公開,可以部分程序用C或C++編寫,然后在Python程序中使用它們。
可嵌入性:可以把Python嵌入C/C++程序,從而向程序用戶提供腳本功能。
豐富的庫:Python標準庫確實很龐大。它可以幫助處理各種工作,包括正則表達式、文檔生成、單元測試、線程、數據庫、網頁瀏覽器、CGI、FTP、電子郵件、XML、XML-RPC、HTML、WAV文件、密碼系統、GUI(圖形用戶界面)、Tk和其他與系統有關的操作。這被稱作Python的“功能齊全”理念。除了標準庫以外,還有許多其他高質量的庫,如wxPython、Twisted和Python圖像庫等等。
規范的代碼:Python采用強制縮進的方式使得代碼具有較好可讀性。而Python語言寫的程序不需要編譯成二進制代碼。
缺點
單行語句和命令行輸出問題:很多時候不能將程序連寫成一行,如import sys;for i in sys.path:print i。而perl和awk就無此限制,可以較為方便的在shell下完成簡單程序,不需要如Python一樣,必須將程序寫入一個.py文件。
獨特的語法
這也許不應該被稱為局限,但是它用縮進來區分語句關系的方式還是給很多初學者帶來了困惑。即便是很有經驗的Python程序員,也可能陷入陷阱當中。
運行速度慢:這里是指與C和C++相比。
應用
系統編程:提供API(Application Programming Interface應用程序編程接口),能方便進行系統維護和管理,Linux下標志性語言之一,是很多系統管理員理想的編程工具。
圖形處理:有PIL、Tkinter等圖形庫支持,能方便進行圖形處理。
數學處理:NumPy擴展提供大量與許多標準數學庫的接口。
文本處理:python提供的re模塊能支持正則表達式,還提供SGML,XML分析模塊,許多程序員利用python進行XML程序的開發。
數據庫編程:程序員可通過遵循Python DB-API(數據庫應用程序編程接口)規范的模塊與Microsoft SQL Server,Oracle,Sybase,DB2,MySQL、SQLite等數據庫通信。python自帶有一個Gadfly模塊,提供了一個完整的SQL環境。
網絡編程:提供豐富的模塊支持sockets編程,能方便快速地開發分布式應用程序。很多大規模軟件開發計劃例如Zope,Mnet 及BitTorrent. Google都在廣泛地使用它。
Web編程:應用的開發語言,支持最新的XML技術。
多媒體應用:Python的PyOpenGL模塊封裝了“OpenGL應用程序編程接口”,能進行二維和三維圖像處理。PyGame模塊可用于編寫游戲軟件。
pymo引擎:PYMO全稱為python memories off,是一款運行于Symbian S60V3,Symbian3,S60V5, Symbian3, Android系統上的AVG游戲引擎。因其基于python2.0平臺開發,并且適用于創建秋之回憶(memories off)風格的AVG游戲,故命名為PYMO。
黑客編程:python有一個hack的庫,內置了你熟悉的或不熟悉的函數,但是缺少成就感。
用Python寫簡單爬蟲
首先,要通過urllib2這個Module獲得對應的HTML源碼。(PS:在python3.3之后urllib2已經不能再用,代之以urllib)
1
2
3
4
import urllib2 #調用urllib2
url='http://www.baidu.com/s?wd=cloga' #把等號右邊的網址賦值給url
html=urllib2.urlopen(url).read() #html隨意取名 等號后面的動作是打開源代碼頁面,并閱讀
print html #打印
通過上面這三句就可以將URL的源碼存在content變量中,其類型為字符型。
接下來是要從這堆HTML源碼中提取我們需要的內容。用Chrome查看一下對應的內容的代碼(也可以用Firefox的Firebug)。
可以看到url的信息存儲在span標簽中,要獲取其中的信息可以用正則式。
Go語言實戰》讀書筆記,未完待續,第一時間看后續筆記。
對于協作開發或者代碼共享來說,文檔是一個可以幫助開發者快速了解以及使用這些代碼的一個教程,文檔越全面,越詳細,入門越快,效率也會更高。
在Go語言中,Go為我們提供了快速生成文檔以及查看文檔的工具,讓我們可以很容易的編寫查看文檔。
Go提供了兩種查看文檔的方式,一種是使用go doc命令在終端查看,這種適用于使用VIM等工具在終端開發的人員,它們不用離開終端,既可以查看想查看的文檔,又可以編碼。
第二種方式,是使用瀏覽器查看的方式,通過godoc命令可以在本機啟動一個web服務,我們可以通過打開瀏覽器,訪問這個服務來查看我們的Go文檔。
這種方式適用于在終端開發的,它們一般不像離開終端,查完即可繼續編碼,這時候使用go doc命令是很不錯的選擇。
? hello go help doc
usage: go doc [-u] [-c] [package|[package.]symbol[.method]]
Doc prints the documentation comments associated with the item identified by its
arguments (a package, const, func, type, var, or method) followed by a one-line
summary of each of the first-level items "under" that item (package-level
declarations for a package, methods for a type, etc.).
Flags:
-c
Respect case when matching symbols.
-cmd
Treat a command (package main) like a regular package.
Otherwise package main's exported symbols are hidden
when showing the package's top-level documentation.
-u
Show documentation for unexported as well as exported
symbols and methods.
從以上可以看出,go doc的使用比較簡單,接收的參數是包名,或者以包里的結構圖、方法等。如果我們不輸入任何參數,那么顯示的是當前目錄的文檔,下面看個例子。
/*
提供的常用庫,有一些常用的方法,方便使用
*/
package lib
// 一個加法實現
// 返回a+b的值
func Add(a,b int) int {
return a+b
}
? lib go doc
package lib // import "flysnow.org/hello/lib"
提供的常用庫,有一些常用的方法,方便使用
func Add(a, b int) int
在當前目錄執行go doc,輸出了當前目錄下的文檔信息。
除此之外,我們還可以指定一個包,就可以列出當前這個包的信息,著包括文檔、方法、結構體等。
? lib go doc json
package json // import "encoding/json"
Package json implements encoding and decoding of JSON as defined in RFC
4627. The mapping between JSON and Go values is described in the
documentation for the Marshal and Unmarshal functions.
See "JSON and Go" for an introduction to this package:
https://golang.org/doc/articles/json_and_go.html
func Compact(dst *bytes.Buffer, src []byte) error
func HTMLEscape(dst *bytes.Buffer, src []byte)
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error
func Marshal(v interface{}) ([]byte, error)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
func Unmarshal(data []byte, v interface{}) error
type Decoder struct{ ... }
func NewDecoder(r io.Reader) *Decoder
type Delim rune
type Encoder struct{ ... }
func NewEncoder(w io.Writer) *Encoder
type InvalidUTF8Error struct{ ... }
type InvalidUnmarshalError struct{ ... }
type Marshaler interface{ ... }
type MarshalerError struct{ ... }
type Number string
type RawMessage []byte
type SyntaxError struct{ ... }
type Token interface{}
type UnmarshalFieldError struct{ ... }
type UnmarshalTypeError struct{ ... }
type Unmarshaler interface{ ... }
type UnsupportedTypeError struct{ ... }
type UnsupportedValueError struct{ ... }
以上是我們以json包為例,查看該包的文檔,從中我們可以看到它有一個名為Decoder的結構體,我們進一步查看這個結構體的文檔。
? lib go doc json.Decoder
package json // import "encoding/json"
type Decoder struct {
// Has unexported fields.
}
A Decoder reads and decodes JSON values from an input stream.
func NewDecoder(r io.Reader) *Decoder
func (dec *Decoder) Buffered() io.Reader
func (dec *Decoder) Decode(v interface{}) error
func (dec *Decoder) More() bool
func (dec *Decoder) Token() (Token, error)
func (dec *Decoder) UseNumber()
現在我們看到這個Decoder有很多方法,進一步查看這些方法的文檔,比如Decode。
? lib go doc json.Decoder.Decode
func (dec *Decoder) Decode(v interface{}) error
Decode reads the next JSON-encoded value from its input and stores it in the
value pointed to by v.
See the documentation for Unmarshal for details about the conversion of JSON
into a Go value.
go doc使用就是這樣,一步步,縮小范圍,查看想看的那些包、結構體、接口或者函數方法的文檔。
go doc終端查看的方式,雖然也很便捷,不過效率不高,并且沒有查看細節以及進行跳轉,為此Go為我們提供了基于瀏覽器使用的網頁方式進行瀏覽API 文檔,我們只用點點鼠標,就可以查看了,還可以在方法、包等之間進行跳轉,更簡潔方便。
要想啟動一個Web在線API文檔服務很簡單,使用godoc就可以了。
? lib godoc -http=:6060
后面的http是要指定Web服務監聽的IP和Port,運行后,我們就可以打開瀏覽器,輸入http://127.0.0.1:6060進行訪問了,你會發現打開的頁面,和GoLang的官方網站一樣,沒錯,這個其實就是官網的一個拷貝,但是包的文檔http://127.0.0.1:6060/pkg/會和官網不一樣,你自己啟動的這個服務,是基于你電腦上GOROOT和GOPATH這兩個路徑下的所有包生成的文檔,會比官網只是標準庫的文檔要多。
在線瀏覽API文檔非常方便,只需要鼠標點擊就可以了,也可以點擊藍色的超鏈接在方法、結構、接口以及包等之間跳轉,還可以查看對應的源代碼,示例代碼,很方便,我們經常用的也是這個在線瀏覽方式。
Go文檔工具,還有一個亮點,就是可以支持開發人員自己寫的代碼,只要開發者按照一定的規則,就可以自動生成文檔了。
在我們編碼中,文檔就是注釋,Go語言采用了和C、Java差不多的注釋風格。一種是雙斜線的方式,一種是斜線和星號的方式。
/*
提供的常用庫,有一些常用的方法,方便使用
*/
package lib
// 一個加法實現
// 返回a+b的值
func Add(a,b int) int {
return a+b
}
這還是我們剛剛那個例子,例子中文檔的編寫的兩種風格。想要為哪些標識符生車文檔,就在哪些標識符之前,使用注釋的方式,加入到代碼中即可。
現在我們不管是用go doc,還是godoc都可以看到我們剛剛注釋的文檔了。
我們在看很多官方API文檔的時候,可以在文檔里看到一些例子,這些例子會告訴我們怎么使用API,以及這個例子打印的輸出是什么,我覺得這個非常好,這樣看函數文檔看不懂的,可以參考這個例子,那么對于我們自己寫的API,怎么給API文檔添加示例代碼呢?
這里我參考了官方的源代碼,總結了測試了一下,發現可行,這里分享一下。
說了這三個規則,下面通過一個例子更直觀的了解。
package lib
import "fmt"
func Example() {
sum:=Add(1,2)
fmt.Println("1+2=",sum)
//Output:
//1+2=3
}
這就是為剛剛那個Add函數寫的示例代碼,我們運行godoc就可以看到結果了。
Go的文檔工具非常強大,更多功能,我們可以使用幫助命令查看。這里再推薦一個比較不錯的第三方的API文檔網站,收錄了包括官方在內的很多Go庫,可以直接跳轉,關聯源代碼,非常方便。https://gowalker.org/
《Go語言實戰》讀書筆記,未完待續,第一時間看后續筆記。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。