1. 程式人生 > >Golang -- 10件你不知道的事情

Golang -- 10件你不知道的事情

10 things you (probably) don’t know about golang

匿名結構體 (Anonymous structs)

Template data (模板資料)

data := struct {
    Title string
    Users []*User //猜測 User 是一個介面,介面指標的切片
} {
    title,
    USERS,
}
err := tmpl.Execute(w, data)

(Cheaper and safer than using map[string]interface{})
確實沒有太理解,是什麼意思?

嵌入式鎖 (Embedded Lock)

var hits struct{
    sync.Mutex   //匿名物件的方法,(類似於一種繼承的方式)
    n int
}
hits.Lock()  // (和C++中的繼承很類似)
hits.n++
hits.Unlock()

Nested structs 巢狀的結構

Decoding deeply nested JSON data

{"data" : {"children" : [
 {"data" : {
  "title" : "The Go homepage",
  "url" : "http://golang.org/"
}
} , ... ]
}
}
type Item struct{
    Titel  string
    URL    string
}
type Response struct{  //使用 Structs 來表示 Json 資料,十分巧妙
    Data struct {
        Children []struct {
            Data Item
        }
    }
}
記得在 golang的 json 庫中,既可以直接使用這樣的結構來進行 JSON 資料的解析和傳送。

Command-line godoc 命令列 godoc

% godoc sync Mutex //這裡 godoc 是命令,sync是包名, Mutex是型別名

顯示值如下

type Mutex struct {
// contains filtered or unexported fields }
A Mutex is a mutual exclusion lock. Mutexes can be created as part of
other structures; the zero value for a Mutex is an unlocked mutex.

func (m *Mutex) Lock()
Lock locks m. If the lock is already in use, the calling goroutine
blocks until the mutex is available.

func (m *Mutex) Unlock()
Unlock unlocks m. It is a run-time error if m is not locked on entry to
Unlock.

A locked Mutex is not associated with a particular goroutine. It is
allowed for one goroutine to lock a Mutex and then arrange for another
goroutine to unlock it.

godc -src 可以直接顯示golang的原始碼

% godoc -src sync Mutex

顯示如下:

// A Mutex is a mutual exclusion lock. // Mutexes can be created as
part of other structures // the zero value for a Mutex is an unlocked
mutex. type Mutex struct {
state int32
sema uint32 }// Local per-P Pool appendix. type poolLocal struct {
Mutex // Protects shared.
// contains filtered or unexported fields }

可以看到,顯示了 unexported state! 便於我們對原始碼進行深入的探索.

Mock out the file system (模仿檔案系統)

現在有一個 package,這個包需要和 file system 進行協作,但是你不想讓你的測試使用真正的磁碟,應該怎麼辦?

var fs fileSystem = osFS{}

type fileSystem interface {  //應該是標準庫中檔案系統的介面
    Open(name string) (file, error)
    Stat(name string) (os.fileInfo, error)
}

type file interface { //標準庫 中 file的介面
    io.Closer
    io.Reader
    io.ReaderAt
    io.Seeker
    Stat() (os.FileInfo, error)
}

type osFs struct{} // osFs 型別,實現了 fileSystem 介面。
func (osFs) Open(name string) (file, error)  //只要它的返回值,實現file介面即可
func (osFs) Stat(name string) (os.FileInfo, error) 

Method expression (方法表示式)

type T struct{}  //定義了一個新的型別 T
func (T) Foo(string) {fmt.Println(s)}  //定義了這個型別T的一個方法
//fn 是一個函式型別的變數,將這個方法賦值給這個變數
// 值得注意的是: fn 的型別是 func(T, string)
// Foo 函式的型別是: func (T) Foo(string) 型別
var fn func(T, sring) = T.Foo  

os/exec 中的一個真實的例子:

func (c *Cmd) stdin() (f *os.File, error)
func (c *Cmd) stdout() (f *os.File, error)
func (c *Cmd) stderr() (f *os.File, error)

type F func(*cmd) (*os.File, error)
for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} { //定義了一個切片
    fd, err := steupFd(c)
    if err != nil {
        c.closeDescriptors(c.closeAfterStart)
        c.closeDescriptors(c.closeAfterWait)
        return err
    }
    c.childFiles = append(c.childFiles, fd)
}

Send and receive on the same channel

package main

import "fmt"

var battle = make(chan string) //定義一個 channel 變數

func warrior(name string, done chan struct{}) {
    //現在問題來了:同一個select中,同一個 channel的情況應該如何應對?
    select {
    case oppoent := <-battle:  //battle 接受資料
        fmt.Printf("%s beat %s\n", name, oppoent)
    case battle <- name:  //battle 傳送資料
        // I lost
    }
    done <- struct{}{}
}

func main() {
    done := make(chan struct{})
    langs := []string{"Go", "C", "C++", "Java", "Perl", "Python"}
    for _, s := range langs {
        go warrior(s, done) //生成多個 Goroutine
    }
    for _ = range langs {
        <-done  //等待 Goroutine結束, 什麼時候整個程序結束?
    }
}

多次執行程式,輸出並不一樣:
第一次執行:

Java beat C++
Go beat C
Perl beat Python

第二次執行:

Python beat Perl
Go beat C
C++ beat Java

現在問題是:
1. 在同一個Select中,如果同時又兩個 或者兩個 以上的 case 語句中: 同樣一個 channel 進行接收資料 會怎麼樣?
2. 同樣一個 channel 同時傳送資料,會怎麼樣?
3. 同樣一個 channel (傳送 和 接收 ) 資料,會怎麼樣? 會有自發自收的現象嗎?

自己的猜測:
1. 如果有兩個 channel 同時傳送資料,會隨機選擇一個 channel 進行傳送資料
2. 如果有兩個 channel 同時接收資料,會隨機選擇一個 channel 進行 接收資料
3. 同一個 channel (傳送和接收) 資料,不會生成自發自收現象, 將會阻塞在這個 channel 中。
對於上述的例子, 具體Select是如何工作的,不太懂?(求高手指教)

Using close to broadcast (使用 close 進行廣播)

package main

import (
    "fmt"
    "math/rand"  //這樣子寫
    "time"
)

func waiter(i int, block, done chan struct{}) {
    time.Sleep(time.Duration(rand.Intn(3000)) * time.Millisecond)
    fmt.Println(i, "waiting...")
    <-block  //所有的 goroutine 會阻塞在 block這裡,直到 close(block)
    fmt.Println(i, "done!")
    done <- struct{}{}
}

func main() {
    block, done := make(chan struct{}), make(chan struct{})

    for i := 0; i < 4; i++ {
        go waiter(i, block, done)
    }

    time.Sleep(5 * time.Second)
    close(block)  //關閉的時候,所有阻塞的 block 將會停止阻塞 (相當於進行了一次廣播)
    for i := 0; i < 4; i++ {
        <-done
    }
}
程式輸出如下:
3 waiting...
2 waiting...
1 waiting...
0 waiting...
3 done!
2 done!
1 done!
0 done!
  1. 首先 會讓所有的goroutine 阻塞 在 “<-block這裡”
  2. 當main 的Goroutine 進行 close(block)的時候,所有的block 就不會再次阻塞了
  3. 相當於 使用 close() 進行了一次廣播

NIl channel in select

func worker(i int, ch chan work, quit chan struct{}) {
    for { 
        select {
        case w :=<- ch:
            if quit == nil {  //一個 channel == nil ?
                w.Refuse();
                fmt.Println("worker", i, "refused",w)
                break;
            }
            w.Do();
        case <-quit:
            fmt.Println("worker", i, "quiting")
            quit = nil (賦值成 nil)
        }
    }
}


func main() {
    ch, quit := make(chan work), make(chan struct{})
    go makeWork(ch)
    for i := 0; i < 4; i++ {
        go worker(i, ch, quit)
    }
    time.Sleep(5 * time.Second)
    close(quit)
    time.Sleep(2 * time.Second)
}

上述程式碼有些不太懂得地方。
1. 當一個 close(quit),之後,quit就從一個正常的 channel 變成了 nil 了嗎? (應該不是)
2. 如果 當沒有 quit 沒有返回的時候,工人永遠在幹活。
3. 當 quit 返回了自後,quit = nil,然後接下來如果要命令工人幹活,那麼工人就會退出不幹了。

總結

總體來說,比較難的地方,在於 channel 和 struct。
1. channel 的各種利用,來實現同步 和非同步,併發等功能。只能在程式碼中進行探索了。
2. 有關 golang 的記憶體模型這些東西,不是很理解,很多地方不明不白的。只能等到以後慢慢理解了。