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 的内存模型这些东西,不是很理解,很多地方不明不白的。只能等到以后慢慢理解了。

你可能感兴趣的:(Go)