• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Fork me on GitHub
            隨筆 - 215  文章 - 13  trackbacks - 0
            <2016年8月>
            31123456
            78910111213
            14151617181920
            21222324252627
            28293031123
            45678910


            專注即時通訊及網游服務端編程
            ------------------------------------
            Openresty 官方模塊
            Openresty 標準模塊(Opm)
            Openresty 三方模塊
            ------------------------------------
            本博收藏大部分文章為轉載,并在文章開頭給出了原文出處,如有再轉,敬請保留相關信息,這是大家對原創作者勞動成果的自覺尊重??!如為您帶來不便,請于本博下留言,謝謝配合。

            常用鏈接

            留言簿(1)

            隨筆分類

            隨筆檔案

            相冊

            Awesome

            Blog

            Book

            GitHub

            Link

            搜索

            •  

            積分與排名

            • 積分 - 216769
            • 排名 - 118

            最新評論

            閱讀排行榜

            http://www.jb51.net/article/77664.htm
            1.序言

            Golang作為一門出身名門望族的編程語言新星,像豆瓣的Redis平臺Codis、類Evernote的云筆記leanote等。

            1.1 為什么要學習

            如果有人說X語言比Y語言好,兩方的支持者經常會激烈地爭吵。如果你是某種語言老手,你就是那門語言的“傳道者”,下意識地會保護它。無論承認與否,你都已被困在一個隧道里,你看到的完全是局限的?!缎ど昕说木融H》對此有很好的注腳:

            [Red] These walls are funny. First you hate ‘em, then you get used to ‘em. Enough time passes, you get so you depend on them. That's institutionalized.
            這些墻很有趣。起初你恨它們,之后你習慣了它們。隨著時間流逝,你開始以來它們。這就是體制。
            在你還沒有被完全“體制化”時,為何不多學些語言,哪怕只是淺嘗輒止,潛移默化中也許你的思維壁壘就松動了。不管是Golang還是Ruby還是其他語言,當看到一些語法習慣與之前熟悉的C和Java不同時,的確潛意識里就會產生抵觸情緒,覺得這不好,還是自己習慣的那套好。長此以往,如果不能沖破自己的心理,“坐以待斃”,被時間淘汰恐怕只是早晚的事兒。所以這里的關鍵也 不是非要學習Golang,而是要不斷地學!

            1.2 用什么工具來開發

            Golang也有專門的IDE,但由于最近迷上了Sublime Text神器,所以這里還是用ST來學習Golang。配置步驟與在ST中使用其他語言開發都類似:

            安裝智能提示插件GoSublime
            創建編譯配置腳本
            點Preferences -> Package Settings -> GoSublime -> User Settings中寫入(感覺保存時自動格式化出來的縮進、空格等風格有些“討厭”,所以就禁掉了):

            復制代碼 代碼如下:

            {
                "fmt_enabled": false,
                "env": {   
                    "path":"D:\\Program Files (x86)\\Go\bin"
                }
            }

             

            點新建Build System產生go.sublime-build中寫入:

            {
                "path": "D:\\Program Files (x86)\\Go\\bin",
                "cmd": ["go", "run", "${file}"],
                "selector": "source.go"
            }


            2.你好,世界

             

            Golang版的HelloWorld來了!一眼望去,package和import的聲明方式與Java如出一轍,比較明顯的區別是:func關鍵字、每行末尾沒有分號、Println()大寫的函數名。這個例子雖小,卻“五臟俱全”,后面會逐一分析這個小例子中碰到的Golang語法點。

            復制代碼 代碼如下:

            package main

             

            import "fmt"

            func main() {
                fmt.Println("你好,世界!")
            }


            2.1 運行方式

             

            Golang提供了go run“解釋”執行和go build編譯執行兩種運行方式,所謂的“解釋”執行其實也是編譯出了可執行文件后才執行的。

            復制代碼 代碼如下:

            $ go run helloworld.go

            你好,世界!
            復制代碼 代碼如下:

            $ go build helloworld.go
            $ ls

            helloworld  helloworld.go
            復制代碼 代碼如下:

            $ ./helloworld

            你好,世界!

             

            2.2 Package管理

            上面例子中我們使用的就是fmt包下的Println()函數。Golang約定:我們可以用./或../相對路徑來引自己的package;如果不是相對路徑,那么go會去$GOPATH/src下查找。

            2.3 格式化輸出

            類似C、Java等語言,Golang的fmt包提供了格式化輸出功能,而且像%d、%s等占位符和\t、\r、\n轉義也幾乎完全一致。但Golang的Println不支持格式化,只有Printf支持,所以我們經常會在后面加入\n換行。此外,Golang加入了%T打印值的類型,%v打印數組等集合的所有元素。

            復制代碼 代碼如下:

            package main

             

            import "fmt"
            import "math"

            /**
             * This is Printer!
             * 布爾值:false
             * 二進制:11111111
             * 八進制:377
             * 十六進制:FF
             * 十進制:255
             * 浮點數:3.141593
             * 字符串:printer
             *
             * 對象類型:int,string,bool,float64
             * 集合:[1 2 3 4 5]
             */
            func main() {
                fmt.Println("This is Printer!")

                fmt.Printf("布爾值:%t\n", 1 == 2)
                fmt.Printf("二進制:%b\n", 255)
                fmt.Printf("八進制:%o\n", 255)
                fmt.Printf("十六進制:%X\n", 255)
                fmt.Printf("十進制:%d\n", 255)
                fmt.Printf("浮點數:%f\n", math.Pi)
                fmt.Printf("字符串:%s\n", "printer")

                fmt.Printf("對象類型:%T,%T,%T,%T\n", 1, "hello", true, math.E)
                fmt.Printf("集合:%v\n", [5]int{1, 2, 3, 4, 5})
            }


            3.語法基礎

             

            3.1 變量和常量

            雖然Golang是靜態類型語言,卻用類似JavaScript中的var關鍵字聲明變量。而且像同樣是靜態語言的Scala一樣,支持類型自動推斷。有一點很重要的不同是:如果明確指明變量類型的話,類型要放在變量名后面。這有點別扭吧?!后面會看到函數的入參和返回值的類型也要這樣聲明。

            復制代碼 代碼如下:

            package main

             

            import "fmt"

            /**
             * 單變量聲明:num[100], word[hello]
             * 多變量聲明:i[1], i[2], k[3]
             * 推導類型:b1[true], b2[false]
             * 常量:age[20], pi[3.141593]
             */
            func main() {
                var num int = 100
                var word string = "hello"
                fmt.Printf("單變量聲明:num[%d], word[%s]\n", num, word)

                var i, j, k int = 1, 2, 3
                fmt.Printf("多變量聲明:i[%d], i[%d], k[%d]\n", i, j, k)

                var b1 = true
                b2 := false
                fmt.Printf("推導類型:b1[%t], b2[%t]\n", b1, b2)

                const age int = 20
                const pi float32 = 3.1415926
                fmt.Printf("常量:age[%d], pi[%f]\n", age, pi)
            }


            3.2 控制語句

             

            作為最基本的語法要素,Golang的各種控制語句也是特點鮮明。在對C繼承發揚的同時,也有自己的想法融入其中:

            if/switch/for的條件部分都沒有圓括號,但必須有花括號。
            switch的case中不需要break?!禖專家編程》里也“控訴”了C的fall-through問題。既然90%以上的情況都要break,為何不將break作為case的默認行為?而且編程語言后來者也鮮有糾正這一問題的。
            switch的case條件可以是多個值。
            Golang中沒有while。

            復制代碼 代碼如下:

            package main

             

            import "fmt"

            /**
             * testIf: x[2] is even
             * testIf: x[3] is odd
             *
             * testSwitch: One
             * testSwitch: Two
             * testSwitch: Three, Four, Five [3]
             * testSwitch: Three, Four, Five [4]
             * testSwitch: Three, Four, Five [5]
             *
             * 標準模式:[0] [1] [2] [3] [4] [5] [6]
             * While模式:[0] [1] [2] [3] [4] [5] [6]
             * 死循環模式:[0] [1] [2] [3] [4] [5] [6]
             */
            func main() {
                testIf(2)
                testIf(3)
                testSwitch(1)
                testSwitch(2)
                testSwitch(3)
                testSwitch(4)
                testSwitch(5)
                testFor(7)
            }

            func testIf(x int) {
                if x % 2 == 0 {
                    fmt.Printf("testIf: x[%d] is even\n", x)
                } else {
                    fmt.Printf("testIf: x[%d] is odd\n", x)
                }
            }

            func testSwitch(i int) {
                switch i {
                    case 1:
                        fmt.Println("testSwitch: One")
                    case 2:
                        fmt.Println("testSwitch: Two")
                    case 3, 4, 5:
                        fmt.Printf("testSwitch: Three, Four, Five [%d]\n", i)
                    default:
                        fmt.Printf("testSwitch: Invalid value[%d]\n", i)
                }
            }

            func testFor(upper int) {
                fmt.Print("標準模式:")
                for i := 0; i < upper; i++ {
                    fmt.Printf("[%d] ", i)
                }
                fmt.Println()

                fmt.Print("While模式:")
                j := 0
                for j < upper {
                    fmt.Printf("[%d] ", j)
                    j++
                }
                fmt.Println()

                fmt.Print("死循環模式:")
                k := 0
                for {
                    if (k >= upper) {
                        break
                    }
                    fmt.Printf("[%d] ", k)
                    k++
                }
                fmt.Println()
            }


            分號和花括號
            分號由詞法分析器在掃描源代碼過程自動插入的,分析器使用簡單的規則:如果在一個新行前方的最后一個標記是一個標識符(包括像int和float64這樣的單詞)、一個基本的如數值這樣的文字、或break continue fallthrough return ++ – ) }中的一個時,它就會自動插入分號。
            分號的自動插入規則產生了“蝴蝶效應”:所有控制結構的左花括號不都能放在下一行。因為按照上面的規則,這樣做會導致分析器在左花括號的前方插入一個分號,從而引起難以預料的結果。所以Golang中是不能隨便換行的。
            3.3 函數

             

            函數有幾點不同:

            func關鍵字。
            最大的不同就是“倒序”的類型聲明。
            不需要函數原型,引用的函數可以后定義。這一點很好,真不喜歡C語言里要么將“最底層抽象”的函數放在最前面定義,要么寫一堆函數原型聲明在最前面。
            3.4 集合

            Golang提供了數組和Map作為基本數據結構:

            數組中的元素會自動初始化,例如int數組元素初始化為0
            切片(借鑒Python)的區間跟主流語言一樣,都是 “左閉右開”
            用 range()遍歷數組和Map

            復制代碼 代碼如下:

            package main

             

            import "fmt"

            /**
             * Array未初始化:  [0 0 0 0 0]
             * Array賦值:  [0 10 0 20 0]
             * Array初始化:  [0 1 2 3 4 5]
             * Array二維:  [[0 1 2] [1 2 3]]
             * Array切片: [2 3] [0 1 2 3] [2 3 4 5]
             *
             * Map哈希表:map[one:1 two:2 three:3],長度[3]
             * Map刪除元素后:map[one:1 three:3],長度[2]
             * Map打?。?br /> *  one => 1
             *  four => 4
             *  three => 3
             *  five => 5
             */
            func main() {
                testArray()
                testMap()
            }

            func testArray() {
                var a [5]int
                fmt.Println("Array未初始化: ", a)

                a[1] = 10
                a[3] = 20
                fmt.Println("Array賦值: ", a)

                b := []int{0, 1, 2, 3, 4, 5}
                fmt.Println("Array初始化: ", b)

                var c [2][3]int
                for i := 0; i < 2; i++ {
                    for j := 0; j < 3; j++ {
                        c[i][j] = i + j
                    }
                }
                fmt.Println("Array二維: ", c)

                d := b[2:4] // b[3,4]
                e := b[:4]  // b[1,2,3,4]
                f := b[2:]  // b[3,4,5]
                fmt.Println("Array切片:", d, e, f)
            }

            func testMap() {
                m := make(map[string]int)

                m["one"] = 1
                m["two"] = 2
                m["three"] = 3
                fmt.Printf("Map哈希表:%v,長度[%d]\n", m, len(m))

                delete(m, "two")
                fmt.Printf("Map刪除元素后:%v,長度[%d]\n", m, len(m))

                m["four"] = 4
                m["five"] = 5
                fmt.Println("Map打?。?)
                for key, val := range m {
                    fmt.Printf("\t%s => %d\n", key, val)
                }
                fmt.Println()
            }


            3.5 指針和內存分配

             

            Golang中可以使用指針,并提供了兩種內存分配機制:

            new:分配長度為0的空白內存,返回類型T*。
            make:僅用于 切片、map、chan消息管道,返回類型T而不是指針。

            復制代碼 代碼如下:

            package main

             

            import "fmt"

            /**
             * 整數i=[10],指針pInt=[0x184000c0],指針指向*pInt=[10]
             * 整數i=[3],指針pInt=[0x184000c0],指針指向*pInt=[3]
             * 整數i=[5],指針pInt=[0x184000c0],指針指向*pInt=[5]
             *
             * Wild的數組指針: <nil>
             * Wild的數組指針==nil[true]
             *
             * New分配的數組指針: &[]
             * New分配的數組指針[0x18443010],長度[0]
             * New分配的數組指針==nil[false]
             * New分配的數組指針Make后: &[0 0 0 0 0 0 0 0 0 0]
             * New分配的數組元素[3]: 23
             *
             * Make分配的數組引用: [0 0 0 0 0 0 0 0 0 0]
             */
            func main() {
                testPointer()
                testMemAllocate()
            }

            func testPointer() {
                var i int = 10;
                var pInt *int = &i;
                fmt.Printf("整數i=[%d],指針pInt=[%p],指針指向*pInt=[%d]\n",
                                i, pInt, *pInt)

                *pInt = 3
                fmt.Printf("整數i=[%d],指針pInt=[%p],指針指向*pInt=[%d]\n",
                                i, pInt, *pInt)

                i = 5
                fmt.Printf("整數i=[%d],指針pInt=[%p],指針指向*pInt=[%d]\n",
                                i, pInt, *pInt)
            }

            func testMemAllocate() {
                var pNil *[]int
                fmt.Println("Wild的數組指針:", pNil)
                fmt.Printf("Wild的數組指針==nil[%t]\n", pNil == nil)

                var p *[]int = new([]int)
                fmt.Println("New分配的數組指針:", p)
                fmt.Printf("New分配的數組指針[%p],長度[%d]\n", p, len(*p))
                fmt.Printf("New分配的數組指針==nil[%t]\n", p == nil)

                //Error occurred
                //(*p)[3] = 23

                *p = make([]int, 10)
                fmt.Println("New分配的數組指針Make后:", p)
                (*p)[3] = 23
                fmt.Println("New分配的數組元素[3]:", (*p)[3])

                var v []int = make([]int, 10)
                fmt.Println("Make分配的數組引用:", v)
            }


            3.6 面向對象編程

             

            Golang的結構體跟C有幾點不同:

            結構體可以有方法,其實也就相當于OOP中的類了。
            支持帶名稱的初始化。
            用指針訪問結構中的屬性也用”.”而不是”->”,指針就像Java中的引用一樣。
            沒有public,protected,private等訪問權限控制。C也沒有protected,C中默認是public的,private需要加static關鍵字限定。Golang中方法名大寫就是public的,小寫就是private的。
            同時,Golang支持接口和多態,而且接口有別于Java中繼承和實現的方式,而是采取了類似Ruby中更為新潮的Duck Type。只要struct與interface有相同的方法,就認為struct實現了這個接口。就好比只要能像鴨子那樣叫,我們就認為它是一只鴨子一樣。

            復制代碼 代碼如下:

            package main

             

            import (
                "fmt"
                "math"
            )

            // -----------------
            //      Struct
            // -----------------

            type Person struct {
                name    string
                age     int
                email   string
            }

            func (p *Person) getName() string {
                return p.name
            }

            // -------------------
            //      Interface
            // -------------------

            type shape interface {
                area() float64
            }

            type rect struct {
                width float64
                height float64
            }

            func (r *rect) area() float64 {
                return r.width * r.height
            }

            type circle struct {
                radius float64
            }

            func (c *circle) area() float64 {
                return math.Pi * c.radius * c.radius
            }

            // -----------------
            //      Test
            // -----------------

            /**
             * 結構Person[{cdai 30 cdai@gmail.com}],姓名[cdai]
             * 結構Person指針[&{cdai 30 cdai@gmail.com}],姓名[cdai]
             * 用指針修改結構Person為[{carter 40 cdai@gmail.com}]
             *
             * Shape[0]周長為[13.920000]
             * Shape[1]周長為[58.088048]
             */
            func main() {
                testStruct()
                testInterface()
            }

            func testStruct() {
                p1 := Person{"cdai", 30, "cdai@gmail.com"}
                p1 = Person{name: "cdai", age: 30, email: "cdai@gmail.com"}
                fmt.Printf("結構Person[%v],姓名[%s]\n", p1, p1.getName())

                ptr1 := &p1
                fmt.Printf("結構Person指針[%v],姓名[%s]\n", ptr1, ptr1.getName())

                ptr1.age = 40
                ptr1.name = "carter"
                fmt.Printf("用指針修改結構Person為[%v]\n", p1)
            }

            func testInterface() {
                r := rect { width: 2.9, height: 4.8 }
                c := circle { radius: 4.3 }

                s := []shape{ &r, &c }
                for i, sh := range s {
                    fmt.Printf("Shape[%d]周長為[%f]\n", i, sh.area())
                }
            }


            3.7 異常處理

             

            Golang中異常的使用比較簡單,可以用errors.New創建,也可以實現Error接口的方法來自定義異常類型,同時利用函數的多返回值特性可以返回異常類。比較復雜的是defer和recover關鍵字的使用。Golang沒有采取try-catch“包住”可能出錯代碼的這種方式,而是用 延遲處理 的方式。

            用defer調用的函數會以后進先出(LIFO)的方式,在當前函數結束后依次順行執行。defer的這一特點正好可以用來處理panic。當panic被調用時,它將立即停止當前函數的執行并開始逐級解開函數堆棧,同時運行所有被defer的函數。如果這種解開達到堆棧的頂端,程序就死亡了。但是,也可以使用內建的recover函數來重新獲得Go程的控制權并恢復正常的執行。由于僅在解開期間運行的代碼處在被defer的函數之內,recover僅在被延期的函數內部才是有用的。

            復制代碼 代碼如下:

            package main

             

            import (
                "fmt"
                "errors"
                "os"
            )

            /**
             * 自定義Error類型,實現內建Error接口
             * type Error interface {
             *      Error() string
             * }
             */
            type MyError struct {
                arg int
                msg string
            }

            func (e *MyError) Error() string {
                return fmt.Sprintf("%d - %s", e.arg, e.msg)
            }

            /**
             * Failed[*errors.errorString]: Bad Arguments - negative!
             * Success:  16
             * Failed[*main.MyError]: 1000 - Bad Arguments - too large!
             *
             * Recovered! Panic message[Cannot find specific file]
             * 4 3 2 1 0
             */
            func main() {
                // 1.Test error
                args := []int{-1, 4, 1000}
                for _, i := range args {
                    if r, e := testError(i); e != nil {
                        fmt.Printf("Failed[%T]: %v\n", e, e)
                    } else {
                        fmt.Println("Success: ", r)
                    }
                }

                // 2.Test defer
                src, err := os.Open("control.go")
                if (err != nil) {
                    fmt.Printf("打開文件錯誤[%v]\n", err)
                    return
                }
                defer src.Close()
                // use src...

                for i := 0; i < 5; i++ {
                    defer fmt.Printf("%d ", i)
                }

                // 3.Test panic/recover
                defer func() {
                    if r := recover(); r != nil {
                        fmt.Printf("Recovered! Panic message[%s]\n", r)
                    }
                }()

                _, err2 := os.Open("test.go")
                if (err2 != nil) {
                    panic("Cannot find specific file")
                }
            }

            func testError(arg int) (int, error) {
                if arg < 0 {
                    return -1, errors.New("Bad Arguments - negative!")
                } else if arg > 256 {
                    return -1, &MyError{ arg, "Bad Arguments - too large!" }
                } else {
                    return arg * arg, nil
                }
            }


            4.高級特性

             

            上面介紹的只是Golang的基本語法和特性,盡管像控制語句的條件不用圓括號、函數多返回值、switch-case默認break、函數閉包、集合切片等特性相比Java的確提高了開發效率,但這些在其他語言中也都有,并不是Golang能真正吸引人的地方。不僅是Golang,我們學習任何語言當然都是從基本語法特性著手,但學習時要不斷地問自己:使這門語言區別于其他語言的”獨到之處“在哪?這種獨到之處往往反映了語言的設計思想、出發點、要解決的”痛點“,這才是一門語言或任何技術的立足之本。

            4.1 goroutine

            goroutine使用go關鍵字來調用函數,也可以使用匿名函數。可以簡單的把go關鍵字調用的函數想像成pthread_create。如果一個goroutine沒有被阻塞,那么別的goroutine就不會得到執行。也就是說goroutine阻塞時,Golang會切換到其他goroutine執行,這是非常好的特性!Java對類似goroutine這種的協程沒有原生支持,像Akka最害怕的就是阻塞。因為協程不等同于線程,操作系統不會幫我們完成“現場”保存和恢復,所以要實現goroutine這種特性,就要模擬操作系統的行為,保存方法或函數在協程“上下文切換”時的Context,當阻塞結束時才能正確地切換回來。像Kilim等協程庫利用字節碼生成,能夠勝任,而Akka完全是運行時的。

            注意:如果你要真正的并發,需要調用runtime.GOMAXPROCS(CPU_NUM)設置。

            復制代碼 代碼如下:

            package main

             

            import "fmt"

            func main() {
                go f("goroutine")

                go func(msg string) {
                    fmt.Println(msg)
                }("going")

                // Block main thread
                var input string
                fmt.Scanln(&input)
                fmt.Println("done")
            }

            func f(msg string) {
                fmt.Println(msg)
            }


            4.2 原子操作

             

            像Java一樣,Golang支持很多CAS操作。運行結果是unsaftCnt可能小于200,因為unsafeCnt++在機器指令層面上不是一條指令,而可能是從內存加載數據到寄存器、執行自增運算、保存寄存器中計算結果到內存這三部分,所以不進行保護的話有些更新是會丟失的。

            復制代碼 代碼如下:

            package main

             

            import (
                "fmt"
                "time"
                "sync/atomic"
                "runtime"
            )

            func main() {
                // IMPORTANT!!!
                runtime.GOMAXPROCS(4)

                // thread-unsafe
                var unsafeCnt int32 = 0
                for i := 0; i < 10; i++ {
                    go func() {
                        for i := 0; i < 20; i++ {
                            time.Sleep(time.Millisecond)
                            unsafeCnt++
                        }
                    }()
                }
                time.Sleep(time.Second)
                fmt.Println("cnt: ", unsafeCnt)

                // CAS toolkit
                var cnt int32 = 0
                for i := 0; i < 10; i++ {
                    go func() {
                        for i := 0; i < 20; i++ {
                            time.Sleep(time.Millisecond)
                            atomic.AddInt32(&cnt, 1)
                        }
                    }()
                }

                time.Sleep(time.Second)
                cntFinal := atomic.LoadInt32(&cnt)
                fmt.Println("cnt: ", cntFinal)
            }


            神奇CAS的原理
            Golang的AddInt32()類似于Java中AtomicInteger.incrementAndGet(),其偽代碼可以表示如下。二者的基本思想是一致的,本質上是 樂觀鎖:首先,從內存位置M加載要修改的數據到寄存器A中;然后,修改數據并保存到另一寄存器B;最終,利用CPU提供的CAS指令(Java通過JNI調用到)用一條指令完成:1)A值與M處的原值比較;2)若相同則將B值覆蓋到M處。
            若不相同,則CAS指令會失敗,說明從內存加載到執行CAS指令這一小段時間內,發生了上下文切換,執行了其他線程的代碼修改了M處的變量值。那么重新執行前面幾個步驟再次嘗試。
            ABA問題:即另一線程修改了M位置的數據,但是從原值改為C,又從C改回原值。這樣上下文切換回來,CAS指令發現M處的值“未改變”(實際是改了兩次,最后改回來了),所以CAS指令正常執行,不會失敗。這種問題在Java中可以用AtomicStampedReference/AtomicMarkableReference解決。
            復制代碼 代碼如下:

            public final int incrementAndGet() {
                for (;;) {
                    int current = get();
                    int next = current + 1;
                    if (compareAndSet(current, next))
                        return next;
                }
            }

            4.3 Channel管道

             

            通過前面可以看到,盡管goroutine很方便很高效,但如果濫用的話很可能會導致并發安全問題。而Channel就是用來解決這個問題的,它是goroutine之間通信的橋梁,類似Actor模型中每個Actor的mailbox。多個goroutine要修改一個狀態時,可以將請求都發送到一個Channel里,然后由一個goroutine負責順序地修改狀態。

            Channel默認是阻塞的,也就是說select時如果沒有事件,那么當前goroutine會發生讀阻塞。同理,Channel是有大小的,當Channel滿了時,發送方會發生寫阻塞。Channel這種阻塞的特性加上goroutine可以很容易就能實現生產者-消費者模式。

            用case可以給Channel設置阻塞的超時時間,避免一直阻塞。而default則使select進入無阻塞模式。

            復制代碼 代碼如下:

            package main

             

            import (
                "fmt"
                "time"
            )

            /**
             * Output:
             * received message: hello
             * received message: world
             *
             * received from channel-1: Hello
             * received from channel-2: World
             *
             * received message: hello
             * Time out!
             *
             * Nothing received!
             * received message: hello
             * Nothing received!
             * Nothing received!
             * Nothing received!
             * Nothing received!
             * Nothing received!
             * Nothing received!
             * Nothing received!
             * Nothing received!
             * Nothing received!
             * received message: world
             * Nothing received!
             * Nothing received!
             * Nothing received!
             */
            func main() {
                listenOnChannel()
                selectTwoChannels()

                blockChannelWithTimeout()
                unblockChannel()
            }

            func listenOnChannel() {
                // Specify channel type and buffer size
                channel := make(chan string, 5)

                go func() {
                    channel <- "hello"
                    channel <- "world"
                }()

                for i := 0; i < 2; i++ {
                    msg := <- channel
                    fmt.Println("received message: " + msg)
                }
            }

            func selectTwoChannels() {
                c1 := make(chan string)
                c2 := make(chan string)

                go func() {
                    time.Sleep(time.Second)
                    c1 <- "Hello"
                }()
                go func() {
                    time.Sleep(time.Second)
                    c2 <- "World"
                }()

                for i := 0; i < 2; i++ {
                    select {
                        case msg1 := <- c1:
                            fmt.Println("received from channel-1: " + msg1)
                        case msg2 := <- c2:
                            fmt.Println("received from channel-2: " + msg2)
                    }
                }
            }

            func blockChannelWithTimeout() {
                channel := make(chan string, 5)

                go func() {
                    channel <- "hello"
                    // Sleep 10 sec
                    time.Sleep(time.Second * 10)
                    channel <- "world"
                }()

                for i := 0; i < 2; i++ {
                    select {
                        case msg := <- channel:
                            fmt.Println("received message: " + msg)
                        // Set timeout 5 sec
                        case <- time.After(time.Second * 5):
                            fmt.Println("Time out!")
                    }
                }
            }

            func unblockChannel() {
                channel := make(chan string, 5)

                go func() {
                    channel <- "hello"
                    time.Sleep(time.Second * 10)
                    channel <- "world"
                }()

                for i := 0; i < 15; i++ {
                    select {
                        case msg := <- channel:
                            fmt.Println("received message: " + msg)
                        default:
                            fmt.Println("Nothing received!")
                            time.Sleep(time.Second)
                    }
                }
            }


            4.4 緩沖流

             

            Golang的bufio包提供了方便的緩沖流操作,通過strings或網絡IO得到流后,用bufio.NewReader/Writer()包裝:

            緩沖區:Peek()或Read時,數據會從底層進入到緩沖區。緩沖區默認大小為4096字節。
            切片和拷貝:Peek()和ReadSlice()得到的都是切片(緩沖區數據的引用)而不是拷貝,所以更加節約空間。但是當緩沖區數據變化時,切片也會隨之變化。而ReadBytes/String()得到的都是數據的拷貝,可以放心使用。
            Unicode支持:ReadRune()可以直接讀取Unicode字符。有意思的是Golang中Unicode字符也要用單引號,這點與Java不同。
            分隔符:ReadSlice/Bytes/String()得到的包含分隔符,bufio不會自動去掉。
            Writer:對應地,Writer提供了WriteBytes/String/Rune。
            undo方法:可以將讀出的字節再放回到緩沖區,就像什么都沒發生一樣。

            復制代碼 代碼如下:

            package main

             

            import (
                "fmt"
                "strings"
                "bytes"
                "bufio"
            )

            /**
             * Buffered: 0
             * Buffered after peek: 7
             * ABCDE
             * AxCDE
             *
             * abcdefghijklmnopqrst 20 <nil>
             * uvwxyz1234567890     16 <nil>
             *                      0  EOF
             *
             * "ABC "
             * "DEF "
             * "GHI"
             *
             * "ABC "
             * "DEF "
             * "GHI"
             *
             * read unicode=[你], size=[3]
             * read unicode=[好], size=[3]
             * read(after undo) unicode=[好], size=[3]
             *
             * Available: 4096
             * Buffered: 0
             * Available after write: 4088
             * Buffered after write: 8
             * Buffer after write: ""
             * Available after flush: 4096
             * Buffered after flush: 0
             * Buffer after flush: "ABCDEFGH"
             *
             * Hello,世界!
             */
            func main() {
                testPeek()
                testRead()
                testReadSlice()
                testReadBytes()
                testReadUnicode()

                testWrite()
                testWriteByte()
            }

            func testPeek() {
                r := strings.NewReader("ABCDEFG")
                br := bufio.NewReader(r)

                fmt.Printf("Buffered: %d\n", br.Buffered())

                p, _ := br.Peek(5)
                fmt.Printf("Buffered after peek: %d\n", br.Buffered())
                fmt.Printf("%s\n", p)

                p[1] = 'x'
                p, _ = br.Peek(5)
                fmt.Printf("%s\n", p)
            }

            func testRead() {
                r := strings.NewReader("abcdefghijklmnopqrstuvwxyz1234567890")
                br := bufio.NewReader(r)
                b := make([]byte, 20)

                n, err := br.Read(b)
                fmt.Printf("%-20s %-2v %v\n", b[:n], n, err)

                n, err = br.Read(b)
                fmt.Printf("%-20s %-2v %v\n", b[:n], n, err)

                n, err = br.Read(b)
                fmt.Printf("%-20s %-2v %v\n", b[:n], n, err)
            }

            func testReadSlice() {
                r := strings.NewReader("ABC DEF GHI")
                br := bufio.NewReader(r)

                w, _ := br.ReadSlice(' ')
                fmt.Printf("%q\n", w)

                w, _ = br.ReadSlice(' ')
                fmt.Printf("%q\n", w)

                w, _ = br.ReadSlice(' ')
                fmt.Printf("%q\n", w)
            }

            func testReadBytes() {
                r := strings.NewReader("ABC DEF GHI")
                br := bufio.NewReader(r)

                w, _ := br.ReadBytes(' ')
                fmt.Printf("%q\n", w)

                w, _ = br.ReadSlice(' ')
                fmt.Printf("%q\n", w)

                s, _ := br.ReadString(' ')
                fmt.Printf("%q\n", s)
            }

            func testReadUnicode() {
                r := strings.NewReader("你好,世界!")
                br := bufio.NewReader(r)

                c, size, _ := br.ReadRune()
                fmt.Printf("read unicode=[%c], size=[%v]\n", c, size)

                c, size, _ = br.ReadRune()
                fmt.Printf("read unicode=[%c], size=[%v]\n", c, size)

                br.UnreadRune()
                c, size, _ = br.ReadRune()
                fmt.Printf("read(after undo) unicode=[%c], size=[%v]\n", c, size)
            }

            func testWrite() {
                b := bytes.NewBuffer(make([]byte, 0))
                bw := bufio.NewWriter(b)

                fmt.Printf("Available: %d\n", bw.Available())
                fmt.Printf("Buffered: %d\n", bw.Buffered())

                bw.WriteString("ABCDEFGH")
                fmt.Printf("Available after write: %d\n", bw.Available())
                fmt.Printf("Buffered after write: %d\n", bw.Buffered())
                fmt.Printf("Buffer after write: %q\n", b)

                bw.Flush()
                fmt.Printf("Available after flush: %d\n", bw.Available())
                fmt.Printf("Buffered after flush: %d\n", bw.Buffered())
                fmt.Printf("Buffer after flush: %q\n", b)
            }

            func testWriteByte() {
                b := bytes.NewBuffer(make([]byte, 0))
                bw := bufio.NewWriter(b)

                bw.WriteByte('H')
                bw.WriteByte('e')
                bw.WriteByte('l')
                bw.WriteByte('l')
                bw.WriteByte('o')
                bw.WriteString(",")
                bw.WriteRune('世')
                bw.WriteRune('界')
                bw.WriteRune('!')
                bw.Flush()

                fmt.Println(b)
            }


            4.5 并發控制

             

            sync包中的WaitGroup是個很有用的類,類似信號量。wg.Add()和Done()能夠加減WaitGroup(信號量)的值,而Wait()會掛起當前線程直到信號量變為0。下面的例子用WaitGroup的值表示正在運行的goroutine數量。在goroutine中,用defer Done()確保goroutine正?;虍惓M顺鰰r,WaitGroup都能減一。

            復制代碼 代碼如下:

            package main

             

            import (
                "fmt"
                "sync"
            )

            /**
             * I'm waiting all goroutines on wg done
             * I'm done=[0]
             * I'm done=[1]
             * I'm done=[2]
             * I'm done=[3]
             * I'm done=[4]
             * I'm done=[5]
             * I'm done=[6]
             * I'm done=[7]
             * I'm done=[8]
             * I'm done=[9]
             */
            func main() {
                var wg sync.WaitGroup
                for i := 0; i < 10; i++ {
                    wg.Add(1)
                    go func(id int) {
                        defer wg.Done()
                        fmt.Printf("I'm done=[%d]\n", id)
                    }(i)
                }

                fmt.Println("I'm waiting all goroutines on wg done")
                wg.Wait()
            }


            4.6 網絡編程

             

            Golang的net包的抽象層次還是挺高的,用不了幾行代碼就能實現一個簡單的TCP或HTTP服務端了。

            4.6.1 Socket編程

            復制代碼 代碼如下:

            package main

             

            import (
                "net"
                "fmt"
                "io"
            )

            /**
             * Starting the server
             * Accept the connection:  127.0.0.1:14071
             * Warning: End of data EOF
             */
            func main() {
                listener, err := net.Listen("tcp", "127.0.0.1:12345")
                if err != nil {
                    panic("error listen: " + err.Error())
                }
                fmt.Println("Starting the server")

                for {
                    conn, err := listener.Accept()
                    if err != nil {
                        panic("error accept: " + err.Error())      
                    }
                    fmt.Println("Accept the connection: ", conn.RemoteAddr())
                    go echoServer(conn)
                }
            }

            func echoServer(conn net.Conn) {
                buf := make([]byte, 1024)
                defer conn.Close()

                for {
                    n, err := conn.Read(buf)
                    switch err {
                        case nil:
                            conn.Write(buf[0:n])
                        case io.EOF:
                            fmt.Printf("Warning: End of data %s\n", err)
                            return
                        default:
                            fmt.Printf("Error: read data %s\n", err)
                            return
                    }
                }
            }


            4.6.2 Http服務器
            復制代碼 代碼如下:

            package main

             

            import (
                "fmt"
                "log"
                "net/http"
            )

            func main() {
                http.HandleFunc("/hello", handleHello)
                fmt.Println("serving on http://localhost:7777/hello")
                log.Fatal(http.ListenAndServe("localhost:7777", nil))
            }

            func handleHello(w http.ResponseWriter, req *http.Request) {
                log.Println("serving", req.URL)
                fmt.Fprintln(w, "Hello, world!")
            }


            5.結束語

             

            5.1 Golang初體驗

            Golang的某些語法的確很簡潔,像行尾無分號、條件語句無括號、類型推斷、函數多返回值、異常處理、原生協程支持、DuckType繼承等,盡管很多并不是Golang首創,但結合到一起寫起來還是很舒服的。

            當然Golang也有讓人“不爽”的地方。像變量和函數中的類型聲明寫在后面簡直是“反人類”!同樣是顛覆,switch的case默認會break就很實用。另外,因為Golang主要還是想替代C做系統開發,所以像類啊、包啊還是能看到C的影子,例如類聲明只有成員變量而不會包含方法實現等,支持全局函數等,所以有時看到aaa.bbb()還是有點迷糊,不知道aaa是包名還是實例名。

            5.2 如何學習一門語言

            當我們談到學習英語時,想到的可能是背單詞、學語法、練習聽說讀寫。對于編程語言來說,背單詞(關鍵字)、學語法(語法規則)少不了,可聽說讀寫只剩下了“寫”,因為我們說話的對象是“冷冰冰”的計算機。所以唯一的捷徑就是“寫”,不斷地練習!

            此外,學的語言多了也能總結出一些規律。首先是基礎語法,包括了變量和常量、控制語句、函數、集合、OOP、異常處理、控制臺輸入輸出、包管理等。然后是高級特性就差別比較大了。專注高并發的語言就要看并發方面的特性,專注OOP的語言就要看有哪些抽象層次更高的特性等等。還是那句話,基礎語言只能說我們會用,而能夠區別一門語言的高級特性才是它的根本和靈魂,也是我們要著重學習和領悟的地方。

             

            您可能感興趣的文章:


            【玩轉Golang】slice切片的操作——切片的追加、刪除、插入等

            一、一般操作

              1,聲明變量,go自動初始化為nil,長度:0,地址:0,nil

            復制代碼
            func main(){
                var ss []string;
                fmt.Printf("length:%v \taddr:%p \tisnil:%v",len(ss),ss, ss==nil)    
            }
            
            ---
            Running...
            
            length:0     addr:0x0     isnil:true
            Success: process exited with code 0.
            復制代碼

              2,切片的追加,刪除,插入操作

            復制代碼
            func main(){
                var ss []string;
                fmt.Printf("[ local print ]\t:\t length:%v\taddr:%p\tisnil:%v\n",len(ss),ss, ss==nil)    
                print("func print",ss)
                //切片尾部追加元素append elemnt
                for i:=0;i<10;i++{
                    ss=append(ss,fmt.Sprintf("s%d",i));
                }
                fmt.Printf("[ local print ]\t:\tlength:%v\taddr:%p\tisnil:%v\n",len(ss),ss, ss==nil)    
                print("after append",ss)
                //刪除切片元素remove element at index
                index:=5;
                ss=append(ss[:index],ss[index+1:]...)
                print("after delete",ss)
                //在切片中間插入元素insert element at index;
            //注意:保存后部剩余元素,必須新建一個臨時切片
                rear:=append([]string{},ss[index:]...)
                ss=append(ss[0:index],"inserted")
                ss=append(ss,rear...)
                print("after insert",ss)
            }
            func print(msg string,ss []string){
                fmt.Printf("[ %20s ]\t:\tlength:%v\taddr:%p\tisnil:%v\tcontent:%v",msg,len(ss),ss, ss==nil,ss)    
                fmt.Println()
            }
            ------
            Running...

            [ local print ]    :     length:0    addr:0x0    isnil:true
            [           func print ]    :    length:0    addr:0x0    isnil:true    content:[]
            [ local print ]    :    length:10    addr:0xc208056000    isnil:false
            [         after append ]    :    length:10    addr:0xc208056000    isnil:false    content:[s0 s1 s2 s3 s4 s5 s6 s7 s8 s9]
            [         after delete ]    :    length:9    addr:0xc208056000    isnil:false    content:[s0 s1 s2 s3 s4 s6 s7 s8 s9]
            [         after insert ]    :    length:10    addr:0xc208056000    isnil:false    content:[s0 s1 s2 s3 s4 inserted s6 s7 s8 s9]

            Success: process exited with code 0.
            復制代碼

               3,copy的使用。

            在使用copy復制切片之前,要保證目標切片有足夠的大小,注意是大小,而不是容量,還是看例子:

            復制代碼
            func main() {
                var sa = make ([]string,0);
                for i:=0;i<10;i++{
                    sa=append(sa,fmt.Sprintf("%v",i))
                    
                }
                var da =make([]string,0,10);
                var cc=0;
                cc= copy(da,sa);
                fmt.Printf("copy to da(len=%d)\t%v\n",len(da),da)
                da = make([]string,5)
                cc=copy(da,sa);
                fmt.Printf("copy to da(len=%d)\tcopied=%d\t%v\n",len(da),cc,da)
                 da = make([]string,10)
                cc =copy(da,sa);
                fmt.Printf("copy to da(len=%d)\tcopied=%d\t%v\n",len(da),cc,da)
                
            }
            
            ---
            Running...
            
            copy to da(len=0)    []
            copy to da(len=5)    copied=5    [0 1 2 3 4]
            copy to da(len=10)    copied=10    [0 1 2 3 4 5 6 7 8 9]

            復制代碼

              從上面運行結果,明顯看出,目標切片大小0,容量10,copy不能復制。目標切片大小小于源切片大小,copy就按照目標切片大小復制,不會報錯。

            二、初始大小和容量

              當我們使用make初始化切片的時候,必須給出size。go語言的書上一般都會告訴我們,當切片有足夠大小的時候,append操作是非??斓?。但是當給出初始大小后,我們得到的實際上是一個含有這個size數量切片類型的空元素,看例子:

            復制代碼
            func main(){
                var ss=make([]string,10);
                ss=append(ss,"last");
                print("after append",ss)
                
            }
            ---
            Running...

            [         after append ]    :    length:11    addr:0xc20804c000    isnil:false    content:[          last]
            復制代碼

              實際上,此時我們應該先用下標為切片元素負值。但是如果我們既想有好的效率,有想繼續使用append函數而不想區分是否有空的元素,此時就要請出make的第三個參數,容量,也就是我們通過傳遞給make,0的大小和足夠大的容量數值就行了。

            復制代碼
            func main(){
                var ss=make([]string,0,10);
                ss=append(ss,"last");
                print("after append",ss)
                
            }
            
            ---
            Running...
            
            [         after append ]    :    length:1    addr:0xc20804a000    isnil:false    content:[last]
            復制代碼

             

            三、切片的指針。

              1,當我們用append追加元素到切片時,如果容量不夠,go就會創建一個新的切片變量,看下面程序的執行結果:

            復制代碼
            func main() {
                var sa []string
            fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
                for i:=0;i<10;i++{
                    sa=append(sa,fmt.Sprintf("%v",i))
                    fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
                }
                fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
            
            }

            ---
            Running ...
            addr:0x0 len:0 content:[] addr:0x1030e0c8 len:1 content:[0] addr:0x10328120 len:2 content:[0 1] addr:0x10322180 len:3 content:[0 1 2] addr:0x10322180 len:4 content:[0 1 2 3] addr:0x10342080 len:5 content:[0 1 2 3 4] addr:0x10342080 len:6 content:[0 1 2 3 4 5] addr:0x10342080 len:7 content:[0 1 2 3 4 5 6] addr:0x10342080 len:8 content:[0 1 2 3 4 5 6 7] addr:0x10324a00 len:9 content:[0 1 2 3 4 5 6 7 8] addr:0x10324a00 len:10 content:[0 1 2 3 4 5 6 7 8 9] addr:0x10324a00 len:10 content:[0 1 2 3 4 5 6 7 8 9]

            //很明顯,切片的地址經過了數次改變。
            復制代碼

               2,如果,在make初始化切片的時候給出了足夠的容量,append操作不會創建新的切片:

            復制代碼
            func main() {
                var sa = make ([]string,0,10);
            fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
                for i:=0;i<10;i++{
                    sa=append(sa,fmt.Sprintf("%v",i))
                    fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
                }
                fmt.Printf("addr:%p \t\tlen:%v content:%v\n",sa,len(sa),sa);
            
            }
            復制代碼
            復制代碼
            addr:0x10304140         len:0 content:[]
            addr:0x10304140         len:1 content:[0]
            addr:0x10304140         len:2 content:[0 1]
            addr:0x10304140         len:3 content:[0 1 2]
            addr:0x10304140         len:4 content:[0 1 2 3]
            addr:0x10304140         len:5 content:[0 1 2 3 4]
            addr:0x10304140         len:6 content:[0 1 2 3 4 5]
            addr:0x10304140         len:7 content:[0 1 2 3 4 5 6]
            addr:0x10304140         len:8 content:[0 1 2 3 4 5 6 7]
            addr:0x10304140         len:9 content:[0 1 2 3 4 5 6 7 8]
            addr:0x10304140         len:10 content:[0 1 2 3 4 5 6 7 8 9]
            addr:0x10304140         len:10 content:[0 1 2 3 4 5 6 7 8 9]

            //可見,切片的地址一直保持不變
            復制代碼


               3, 如果不能準確預估切片的大小,又不想改變變量(如:為了共享數據的改變),這時候就要請出指針來幫忙了,下面程序中,sa就是osa這個切片的指針,我們共享切片數據和操作切片的時候都使用這個切片地址就ok了,其本質上是:append操作亦然會在需要的時候構造新的切片,不過是將地址都保存到了sa中,因此我們通過該指針始終可以訪問到真正的數據。

            復制代碼
            func main() {
                var osa = make ([]string,0);
                sa:=&osa;
                for i:=0;i<10;i++{
                    *sa=append(*sa,fmt.Sprintf("%v",i))
                    fmt.Printf("addr of osa:%p,\taddr:%p \t content:%v\n",osa,sa,sa);
                }
                fmt.Printf("addr of osa:%p,\taddr:%p \t content:%v\n",osa,sa,sa);
               
            }


            ---
            Running...

            addr of osa:0xc20800a220,    addr:0xc20801e020      content:&[0]
            addr of osa:0xc20801e0a0,    addr:0xc20801e020      content:&[0 1]
            addr of osa:0xc20803e0c0,    addr:0xc20801e020      content:&[0 1 2]
            addr of osa:0xc20803e0c0,    addr:0xc20801e020      content:&[0 1 2 3]
            addr of osa:0xc208050080,    addr:0xc20801e020      content:&[0 1 2 3 4]
            addr of osa:0xc208050080,    addr:0xc20801e020      content:&[0 1 2 3 4 5]
            addr of osa:0xc208050080,    addr:0xc20801e020      content:&[0 1 2 3 4 5 6]
            addr of osa:0xc208050080,    addr:0xc20801e020      content:&[0 1 2 3 4 5 6 7]
            addr of osa:0xc208052000,    addr:0xc20801e020      content:&[0 1 2 3 4 5 6 7 8]
            addr of osa:0xc208052000,    addr:0xc20801e020      content:&[0 1 2 3 4 5 6 7 8 9]
            addr of osa:0xc208052000,    addr:0xc20801e020      content:&[0 1 2 3 4 5 6 7 8 9]
            復制代碼

             

             

            posted on 2016-01-13 11:18 思月行云 閱讀(271) 評論(0)  編輯 收藏 引用 所屬分類: Golang
            久久久婷婷五月亚洲97号色| 91精品免费久久久久久久久| 久久天天躁狠狠躁夜夜av浪潮| 亚洲乱码日产精品a级毛片久久 | 青青草国产精品久久久久| 国内精品久久久久久99蜜桃| 99久久国产主播综合精品| 国产偷久久久精品专区| 欧美伊香蕉久久综合类网站| 思思久久99热只有频精品66| 91精品国产综合久久精品| 久久国产香蕉视频| 久久91亚洲人成电影网站| 97久久婷婷五月综合色d啪蜜芽| 蜜桃麻豆www久久| 亚洲va久久久噜噜噜久久| 久久久久国产视频电影| 午夜不卡888久久| 国产精品无码久久综合| 麻豆一区二区99久久久久| 思思久久99热免费精品6| 91久久精品国产免费直播| 成人综合伊人五月婷久久| 亚洲伊人久久大香线蕉综合图片| 久久亚洲中文字幕精品一区| 99久久精品久久久久久清纯| 久久精品国产99久久无毒不卡| 久久人人爽人人人人片av| 超级碰碰碰碰97久久久久| 亚洲欧美成人久久综合中文网| 99久久精品免费国产大片| 国产 亚洲 欧美 另类 久久 | 综合久久精品色| 久久免费香蕉视频| 亚洲精品tv久久久久| 久久久精品视频免费观看 | 亚洲一区中文字幕久久| 久久综合久久久| 国产精品热久久无码av| 久久99精品久久久久久野外| 久久精品国产99国产精品|