佳木斯湛栽影视文化发展公司

主頁 > 知識(shí)庫 > Go語言編程入門超級(jí)指南

Go語言編程入門超級(jí)指南

熱門標(biāo)簽:地方門戶網(wǎng)站 鐵路電話系統(tǒng) 百度競(jìng)價(jià)排名 AI電銷 Linux服務(wù)器 呼叫中心市場(chǎng)需求 服務(wù)外包 網(wǎng)站排名優(yōu)化

1.序言

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

1.1 為什么要學(xué)習(xí)

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

[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.
這些墻很有趣。起初你恨它們,之后你習(xí)慣了它們。隨著時(shí)間流逝,你開始以來它們。這就是體制。
在你還沒有被完全“體制化”時(shí),為何不多學(xué)些語言,哪怕只是淺嘗輒止,潛移默化中也許你的思維壁壘就松動(dòng)了。不管是Golang還是Ruby還是其他語言,當(dāng)看到一些語法習(xí)慣與之前熟悉的C和Java不同時(shí),的確潛意識(shí)里就會(huì)產(chǎn)生抵觸情緒,覺得這不好,還是自己習(xí)慣的那套好。長此以往,如果不能沖破自己的心理,“坐以待斃”,被時(shí)間淘汰恐怕只是早晚的事兒。所以這里的關(guān)鍵也 不是非要學(xué)習(xí)Golang,而是要不斷地學(xué)!

1.2 用什么工具來開發(fā)

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

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

復(fù)制代碼 代碼如下:

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

點(diǎn)新建Build System產(chǎn)生go.sublime-build中寫入:

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


2.你好,世界

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

復(fù)制代碼 代碼如下:

package main

import "fmt"

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


2.1 運(yùn)行方式

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

復(fù)制代碼 代碼如下:

$ go run helloworld.go

你好,世界!
復(fù)制代碼 代碼如下:

$ go build helloworld.go
$ ls

helloworld  helloworld.go
復(fù)制代碼 代碼如下:

$ ./helloworld

你好,世界!

2.2 Package管理

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

2.3 格式化輸出

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

復(fù)制代碼 代碼如下:

package main

import "fmt"
import "math"

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

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

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


3.語法基礎(chǔ)

3.1 變量和常量

雖然Golang是靜態(tài)類型語言,卻用類似JavaScript中的var關(guān)鍵字聲明變量。而且像同樣是靜態(tài)語言的Scala一樣,支持類型自動(dòng)推斷。有一點(diǎn)很重要的不同是:如果明確指明變量類型的話,類型要放在變量名后面。這有點(diǎn)別扭吧?!后面會(huì)看到函數(shù)的入?yún)⒑头祷刂档念愋鸵惨@樣聲明。

復(fù)制代碼 代碼如下:

package main

import "fmt"

/**
 * 單變量聲明:num[100], word[hello]
 * 多變量聲明:i[1], i[2], k[3]
 * 推導(dǎo)類型: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("推導(dǎo)類型: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的各種控制語句也是特點(diǎn)鮮明。在對(duì)C繼承發(fā)揚(yáng)的同時(shí),也有自己的想法融入其中:

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

復(fù)制代碼 代碼如下:

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]
 *
 * 標(biāo)準(zhǔn)模式:[0] [1] [2] [3] [4] [5] [6]
 * While模式:[0] [1] [2] [3] [4] [5] [6]
 * 死循環(huán)模式:[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("標(biāo)準(zhǔn)模式:")
    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("死循環(huán)模式:")
    k := 0
    for {
        if (k >= upper) {
            break
        }
        fmt.Printf("[%d] ", k)
        k++
    }
    fmt.Println()
}


分號(hào)和花括號(hào)
分號(hào)由詞法分析器在掃描源代碼過程自動(dòng)插入的,分析器使用簡(jiǎn)單的規(guī)則:如果在一個(gè)新行前方的最后一個(gè)標(biāo)記是一個(gè)標(biāo)識(shí)符(包括像int和float64這樣的單詞)、一個(gè)基本的如數(shù)值這樣的文字、或break continue fallthrough return ++ – ) }中的一個(gè)時(shí),它就會(huì)自動(dòng)插入分號(hào)。
分號(hào)的自動(dòng)插入規(guī)則產(chǎn)生了“蝴蝶效應(yīng)”:所有控制結(jié)構(gòu)的左花括號(hào)不都能放在下一行。因?yàn)榘凑丈厦娴囊?guī)則,這樣做會(huì)導(dǎo)致分析器在左花括號(hào)的前方插入一個(gè)分號(hào),從而引起難以預(yù)料的結(jié)果。所以Golang中是不能隨便換行的。
3.3 函數(shù)

函數(shù)有幾點(diǎn)不同:

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

Golang提供了數(shù)組和Map作為基本數(shù)據(jù)結(jié)構(gòu):

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

復(fù)制代碼 代碼如下:

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 指針和內(nèi)存分配

Golang中可以使用指針,并提供了兩種內(nèi)存分配機(jī)制:

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

復(fù)制代碼 代碼如下:

package main

import "fmt"

/**
 * 整數(shù)i=[10],指針pInt=[0x184000c0],指針指向*pInt=[10]
 * 整數(shù)i=[3],指針pInt=[0x184000c0],指針指向*pInt=[3]
 * 整數(shù)i=[5],指針pInt=[0x184000c0],指針指向*pInt=[5]
 *
 * Wild的數(shù)組指針: nil>
 * Wild的數(shù)組指針==nil[true]
 *
 * New分配的數(shù)組指針: []
 * New分配的數(shù)組指針[0x18443010],長度[0]
 * New分配的數(shù)組指針==nil[false]
 * New分配的數(shù)組指針Make后: [0 0 0 0 0 0 0 0 0 0]
 * New分配的數(shù)組元素[3]: 23
 *
 * Make分配的數(shù)組引用: [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("整數(shù)i=[%d],指針pInt=[%p],指針指向*pInt=[%d]\n",
                    i, pInt, *pInt)

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

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

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

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

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

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

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


3.6 面向?qū)ο缶幊?/strong>

Golang的結(jié)構(gòu)體跟C有幾點(diǎn)不同:

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

復(fù)制代碼 代碼如下:

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
// -----------------

/**
 * 結(jié)構(gòu)Person[{cdai 30 cdai@gmail.com}],姓名[cdai]
 * 結(jié)構(gòu)Person指針[{cdai 30 cdai@gmail.com}],姓名[cdai]
 * 用指針修改結(jié)構(gòu)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("結(jié)構(gòu)Person[%v],姓名[%s]\n", p1, p1.getName())

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

    ptr1.age = 40
    ptr1.name = "carter"
    fmt.Printf("用指針修改結(jié)構(gòu)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中異常的使用比較簡(jiǎn)單,可以用errors.New創(chuàng)建,也可以實(shí)現(xiàn)Error接口的方法來自定義異常類型,同時(shí)利用函數(shù)的多返回值特性可以返回異常類。比較復(fù)雜的是defer和recover關(guān)鍵字的使用。Golang沒有采取try-catch“包住”可能出錯(cuò)代碼的這種方式,而是用 延遲處理 的方式。

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

復(fù)制代碼 代碼如下:

package main

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

/**
 * 自定義Error類型,實(shí)現(xiàn)內(nèi)建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("打開文件錯(cuò)誤[%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.高級(jí)特性

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

4.1 goroutine

goroutine使用go關(guān)鍵字來調(diào)用函數(shù),也可以使用匿名函數(shù)??梢院?jiǎn)單的把go關(guān)鍵字調(diào)用的函數(shù)想像成pthread_create。如果一個(gè)goroutine沒有被阻塞,那么別的goroutine就不會(huì)得到執(zhí)行。也就是說goroutine阻塞時(shí),Golang會(huì)切換到其他goroutine執(zhí)行,這是非常好的特性!Java對(duì)類似goroutine這種的協(xié)程沒有原生支持,像Akka最害怕的就是阻塞。因?yàn)閰f(xié)程不等同于線程,操作系統(tǒng)不會(huì)幫我們完成“現(xiàn)場(chǎng)”保存和恢復(fù),所以要實(shí)現(xiàn)goroutine這種特性,就要模擬操作系統(tǒng)的行為,保存方法或函數(shù)在協(xié)程“上下文切換”時(shí)的Context,當(dāng)阻塞結(jié)束時(shí)才能正確地切換回來。像Kilim等協(xié)程庫利用字節(jié)碼生成,能夠勝任,而Akka完全是運(yùn)行時(shí)的。

注意:如果你要真正的并發(fā),需要調(diào)用runtime.GOMAXPROCS(CPU_NUM)設(shè)置。

復(fù)制代碼 代碼如下:

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操作。運(yùn)行結(jié)果是unsaftCnt可能小于200,因?yàn)閡nsafeCnt++在機(jī)器指令層面上不是一條指令,而可能是從內(nèi)存加載數(shù)據(jù)到寄存器、執(zhí)行自增運(yùn)算、保存寄存器中計(jì)算結(jié)果到內(nèi)存這三部分,所以不進(jìn)行保護(hù)的話有些更新是會(huì)丟失的。

復(fù)制代碼 代碼如下:

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

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

4.3 Channel管道

通過前面可以看到,盡管goroutine很方便很高效,但如果濫用的話很可能會(huì)導(dǎo)致并發(fā)安全問題。而Channel就是用來解決這個(gè)問題的,它是goroutine之間通信的橋梁,類似Actor模型中每個(gè)Actor的mailbox。多個(gè)goroutine要修改一個(gè)狀態(tài)時(shí),可以將請(qǐng)求都發(fā)送到一個(gè)Channel里,然后由一個(gè)goroutine負(fù)責(zé)順序地修改狀態(tài)。

Channel默認(rèn)是阻塞的,也就是說select時(shí)如果沒有事件,那么當(dāng)前goroutine會(huì)發(fā)生讀阻塞。同理,Channel是有大小的,當(dāng)Channel滿了時(shí),發(fā)送方會(huì)發(fā)生寫阻塞。Channel這種阻塞的特性加上goroutine可以很容易就能實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式。

用case可以給Channel設(shè)置阻塞的超時(shí)時(shí)間,避免一直阻塞。而default則使select進(jìn)入無阻塞模式。

復(fù)制代碼 代碼如下:

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或網(wǎng)絡(luò)IO得到流后,用bufio.NewReader/Writer()包裝:

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

復(fù)制代碼 代碼如下:

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 并發(fā)控制

sync包中的WaitGroup是個(gè)很有用的類,類似信號(hào)量。wg.Add()和Done()能夠加減WaitGroup(信號(hào)量)的值,而Wait()會(huì)掛起當(dāng)前線程直到信號(hào)量變?yōu)?。下面的例子用WaitGroup的值表示正在運(yùn)行的goroutine數(shù)量。在goroutine中,用defer Done()確保goroutine正?;虍惓M顺鰰r(shí),WaitGroup都能減一。

復(fù)制代碼 代碼如下:

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 網(wǎng)絡(luò)編程

Golang的net包的抽象層次還是挺高的,用不了幾行代碼就能實(shí)現(xiàn)一個(gè)簡(jiǎn)單的TCP或HTTP服務(wù)端了。

4.6.1 Socket編程

復(fù)制代碼 代碼如下:

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服務(wù)器
復(fù)制代碼 代碼如下:

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.結(jié)束語

5.1 Golang初體驗(yàn)

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

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

5.2 如何學(xué)習(xí)一門語言

當(dāng)我們談到學(xué)習(xí)英語時(shí),想到的可能是背單詞、學(xué)語法、練習(xí)聽說讀寫。對(duì)于編程語言來說,背單詞(關(guān)鍵字)、學(xué)語法(語法規(guī)則)少不了,可聽說讀寫只剩下了“寫”,因?yàn)槲覀冋f話的對(duì)象是“冷冰冰”的計(jì)算機(jī)。所以唯一的捷徑就是“寫”,不斷地練習(xí)!

此外,學(xué)的語言多了也能總結(jié)出一些規(guī)律。首先是基礎(chǔ)語法,包括了變量和常量、控制語句、函數(shù)、集合、OOP、異常處理、控制臺(tái)輸入輸出、包管理等。然后是高級(jí)特性就差別比較大了。專注高并發(fā)的語言就要看并發(fā)方面的特性,專注OOP的語言就要看有哪些抽象層次更高的特性等等。還是那句話,基礎(chǔ)語言只能說我們會(huì)用,而能夠區(qū)別一門語言的高級(jí)特性才是它的根本和靈魂,也是我們要著重學(xué)習(xí)和領(lǐng)悟的地方。

您可能感興趣的文章:
  • Go語言hello world實(shí)例
  • go語言基礎(chǔ)語法示例
  • GO語言入門Golang進(jìn)入HelloWorld

標(biāo)簽:蘭州 銅川 仙桃 衡水 崇左 湘潭 湖南 黃山

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Go語言編程入門超級(jí)指南》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    井陉县| 禹城市| 灵宝市| 桐梓县| 府谷县| 泸州市| 布尔津县| 手游| 泊头市| 天祝| 威海市| 通州区| 顺昌县| 荣成市| 老河口市| 泗洪县| 闽清县| 安岳县| 潜山县| 琼中| 桦川县| 辰溪县| 天全县| 英吉沙县| 朝阳区| 定州市| 浪卡子县| 阳东县| 黎川县| 定陶县| 嘉禾县| 新余市| 饶阳县| 遵义县| 卫辉市| 庆安县| 深泽县| 高要市| 兰州市| 云龙县| 河东区|