[https://log.zvz.im/2018/07/15/go-tensorflow/](https://log.zvz.im/2018/07/15/go-tensorflow/)? ? ??Go 語言學會 Tensorflow?機器學習的庫
# 使用 Go 語言學會 Tensorflow
Tensorflow 并不是一個專門用于機器學習的庫,相反的,它是一個通用的用于圖計算的庫。它的核心部分是用 C++ 實現的,同時還有其它語言的接口庫。Go 語言版本的接口庫與 Python 版本的并不一樣,它不僅有助于我們使用 Go 語言調用 Tensorflow,同時有助于我們了解 Tensorflow 的底層實現。
[](https://ws1.sinaimg.cn/large/7327fe71gy1fsnt4f7jeej209a07uq3u.jpg)
## [](https://log.zvz.im/2018/07/15/go-tensorflow/#%E6%8E%A5%E5%8F%A3%E5%BA%93 "接口庫")接口庫
Tensorflow 官方發布的代碼庫包含:
* C++ 源代碼:Tensorflow 核心功能高層 & 底層操作的代碼實現。
* Python 接口庫 & Python 功能庫:接口庫是通過 C++ 代碼自動生成的,這樣我們可以使用 Python 直接調用到 C++ 的方法:numpy 核心代碼也是這樣實現的。功能庫則是對接口庫方法的組合調用,它實現了大家所熟知的高層 API 接口。
* Java 接口庫
* Go 接口庫
我作為一名 Go 開發者,且不是 Java 愛好者,很自然地選擇了使用 Go 版本的接口庫,研究它能完成哪些任務。
## [](https://log.zvz.im/2018/07/15/go-tensorflow/#Go-%E6%8E%A5%E5%8F%A3%E5%BA%93 "Go 接口庫")Go 接口庫
首件值得注意的事,正如它的維護者們承認的,就是 Go 接口庫缺少對`變量`支持:這些接口被設計成用于**使用**訓練好的模型,而不是從零開始**訓練**模型。這在[Installing Tensorflow for Go](https://www.tensorflow.org/versions/master/install/install_go)中交待得很清楚。
> Tensorflow 提供了 Go 程序接口。這些接口特別適于加載 Python 庫所創建的模型,然后在 Go 應用中執行。
如果我們對于訓練機器學習模型不那么感興趣:那就恰好!不過,若你對訓練模型感興趣的話,這里有一點建議:
> 作為一名真正的 Go 愛好者,應當尋求便宜之道!請使用 Python 來定義和訓練模型;之后,你總是能用 Go 來加載并使用它們的。
簡言之:Go 接口庫可以用來**導入并定義**常量圖;這里說的「常量」是指沒有訓練過程參與,所以沒有可用于訓練的變量。
讓我們立刻開始用 Go 來調用 Tensorflow:創建我們的第一個應用程序。
接下來,我假設你們已經安裝了 Go 環境,并且已經按照[README](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/go/README.md)編譯并安裝了 Tensorflow 的接口庫。
## [](https://log.zvz.im/2018/07/15/go-tensorflow/#%E7%90%86%E8%A7%A3-Tensorflow-%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84 "理解 Tensorflow 的數據結構")理解 Tensorflow 的數據結構
我要在這里重申一下 Tensorflow 的定義(我為大家從[Tensorflow 站點](https://www.tensorflow.org/)的說明中劃出了重點):
> TensorFlow? 是一個使用數據流圖進行數值計算的開源軟件庫。圖中的節點**代表**數學操作,而圖中的邊則**代表**節點間相互聯系的多維數據數組(張量)。
我們可以把 Tensorflow 看作是一種描述性語言,類似于 SQL,你可以用它描述你的需求,讓底層引擎(數據庫)解析你的 query 語句,檢查語法和語義錯誤,將其轉化為它的內部描述,優化并計算出結果:最后返回給你正確的結果。
所以,我們使用 API 接口時,實際是在描述一個圖:當我們將它放入一個`Session`中,并且開始`Run`時,圖的求值過程就開始了。
理解這些之后,讓我們嘗試定義一個計算圖,并且在一個`Session`中計算它。[API 文檔](https://godoc.org/github.com/tensorflow/tensorflow/tensorflow/go)能為我們清楚地提供`tensorflow`(縮寫`tf`)和`op`包的方法列表。
如你所見,這兩個包包含了我們對圖進行定義和計算所需要的一切。
前一個包包含了構建類似`Graph`本身等基礎「空」結構的方法,后一個則是最重要的包,它包含了從 C++ 實現里自動生成的接口方法。
假設我們想要計算矩陣 A 和 x 的乘積:

我假設讀者已經知道 tensorflow 圖定義的概念,知道什么是占位符而且知道它們如何工作。下面的代碼是用戶第一次使用 Python 接口時可能會做的嘗試。我們將其命名為`attempt1.go`
~~~
package main
import (
"fmt"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
"github.com/tensorflow/tensorflow/tensorflow/go/op"
)
func main() {
// 讓我們描述我們的需求:創建圖
// 我們想要定義兩個運行時使用的 placeholder
// 第一個 placeholder A 是 [2, 2] 整數張量
// 第二個 placeholder x 是 [2, 1] 整數張量
// 然后計算 Y = Ax
// 創建圖的節點:一個空節點,作為圖的根節點
root := op.NewScope()
// 定義兩個占位符
A := op.Placeholder(root, tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 2)))
x := op.Placeholder(root, tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 1)))
// 定義可以接受 A & x 作為輸入的操作節點
product := op.MatMul(root, A, x)
// 每次我們將 `Scope` 傳入一個操作時,我們都將這個操作置于這個作用域內。
// 如你所見,我們有一個通過 NewScope 創建空域:
// 這個空域是我們所創建的圖的根,我們用 “/”表示它。
// 現在我們讓 tensorflow 通過我們的定義來構建圖。
// 實體的圖是通過我們用域和操作組合起來定義的“抽象”圖生成的。
graph, err := root.Finalize()
if err != nil {
// 處理這個錯誤沒有什么用處
// 如果我們對圖的定義做錯了,我們只能手動修正這些定義。
// 它很想一個 SQL 查詢過程:如果查詢語句錯了,我們只能重寫它
panic(err.Error())
}
// 至此:我們的圖定義語法上就沒有問題了。
// 我們現在可以將其放入一個 Session 中使用了。
var sess *tf.Session
sess, err = tf.NewSession(graph, &tf.SessionOptions{})
if err != nil {
panic(err.Error())
}
// 為了使用占位符,我們必須創建含有數值的張量傳入網絡中
var matrix, column *tf.Tensor
// A = [ [1, 2], [-1, -2] ]
if matrix, err = tf.NewTensor([2][2]int64{ {1, 2}, {-1, -2} }); err != nil {
panic(err.Error())
}
// x = [ [10], [100] ]
if column, err = tf.NewTensor([2][1]int64{ {10}, {100} }); err != nil {
panic(err.Error())
}
var results []*tf.Tensor
if results, err = sess.Run(map[tf.Output]*tf.Tensor{
A: matrix,
x: column,
}, []tf.Output{product}, nil); err != nil {
panic(err.Error())
}
for _, result := range results {
fmt.Println(result.Value().([][]int64))
}
}
~~~
代碼內的注釋非常豐富,請大家仔細閱讀每行注釋。
如果是 Python 版 Tensorflow 的使用者,現在已經可以期待代碼編譯后能完美運行了。我們看看是否能如愿呢:
`go run attempt1.go`
會得到如下結果:
`panic: failed to add operation "Placeholder": Duplicate node name in graph: 'Placeholder'`
稍等,這里發生了什么?錯誤提示很明顯,有兩個同名的占位符都叫作“PlaceHolder“。
## [](https://log.zvz.im/2018/07/15/go-tensorflow/#%E7%AC%AC%E4%B8%80%E8%AF%BE%EF%BC%9A%E8%8A%82%E7%82%B9-ID "第一課:節點 ID")第一課:節點 ID
使用 Python 接口時,每當我們調用定義操作的方法時,無論它是否已經被調用過,都會生成不同的節點。下面的代碼就會很順利的返回結果 3。
~~~
import tensorflow as tf
a = tf.placeholder(tf.int32, shape=())
b = tf.placeholder(tf.int32, shape=())
add = tf.add(a,b)
sess = tf.InteractiveSession()
print(sess.run(add, feed_dict={a: 1,b: 2}))
~~~
要驗證這段程序創建了兩個不同的節點,我們只需要將占位符的名字打印出來:`print(a.name, b.name)`輸出`Placeholder:0 Placeholder_1:0`。 這里`b`占位符的名字是`Placeholder_1:0`同時`a`占位符的名字是`Placeholder:0`。
在 Go 版本里,則不同,之前程序就因為`A`和`x`都叫作`Placeholder`而導致運行失敗。我們可以總結如下:
**Go 語言版 API 接口每次在我們調用定義操作的方法時,不會自動為節點生成新的名稱**:操作名稱是固定的,而且我們沒法改變它。
**問答時間:**
* 關于 Tensorflow 系統我們學到了什么?對于一個圖來說,它的每一個節點都必須有唯一的名稱。節點是以各自的名字來區分的。
* 節點名稱是否與定義它的操作名稱相同?是的,更確切地講,不完全是,只是名稱的結尾部分相同。
為了說明第二個答案,讓我們來修復節點的重名問題。
## [](https://log.zvz.im/2018/07/15/go-tensorflow/#%E7%AC%AC%E4%BA%8C%E8%AF%BE%EF%BC%9A%E4%BD%9C%E7%94%A8%E5%9F%9F "第二課:作用域")第二課:作用域
正如我們剛才看到,Python 版的 API 接口會在每次定義操作時,自動生成一個新的名字。從底層實現來看,Python 接口調用了 C++ 的`Scope`類的`WithOpName`方法。以下是此方法的文檔和形式聲明,來自[scope.h](https://github.com/tensorflow/tensorflow/blob/a5b1fb8e56ceda0ee2794ee05f5a7642157875c5/tensorflow/cc/framework/scope.h)頭文件:
~~~
/// Return a new scope. All ops created within the returned scope will have
/// names of the form <name>/<op_name>[_<suffix].
Scope WithOpName(const string& op_name) const;
~~~
我們可以注意到這個用于命名節點的方法,其返回值是一個`Scope`對象,由此一個節點的名稱,實際上是一個`Scope`域對象。一個`Scope`是一個**完整路徑**,從根`/`(空圖)起到`op_name`結束。
當我們增加一個從`/`到`op_name`有相同路徑的節點時,會導致在同一個域中的節點重復,此時`WithOpName`方法會為名稱添加一個后綴`_<suffix>`(`<suffix>`是一個計數器)。
知道這些以后,我們期望找到`Scope 類型`的`WithOpName`方法,來解決重復節點的問題。可惜的是,這個方法暫時還沒有實現。
取而代之的,在[文檔中的 Scope 類型](https://godoc.org/github.com/tensorflow/tensorflow/tensorflow/go/op#Scope)部分我們看到唯一能夠返回一個新的`Scope`的方法是`SubScope(namespace string)`。
引用文檔如下:
> 調用 SubScope 方法會返回一個新的 Scope,使得所有加入圖中的操作都被置于命名空間 ‘namespace’ 中。如果命名空間與作用域中已有的命名空間重名,則會加上后綴。
使用后綴進行沖突管理與在 C++ 中使用`WithOpName`方法**不同**:`WithOpName`在同一個作用域內的操作名稱后加上`suffix`后綴(這樣`Placeholder`就變成了`Placeholder_1`),而 Go 使用的`SubScope`的方法則是**對作用域名稱**增加后綴名`suffix`。
這點差異會產生完全不同的圖,不過盡管不同(節點放在不同的作用域中),從計算角度看它們是等價的。
讓我們修改一下占位符的定義過程,定義兩個不同的節點,然后打印出`Scope`的名稱。
讓我們創建文件`attempt2.go`將下面幾行代碼
~~~
A := op.Placeholder(root, tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 2)))
x := op.Placeholder(root, tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 1)))
~~~
改成
~~~
// define 2 subscopes of the root subscopes, called "input". In this
// way we expect to have a input/ and a input_1/ scope under the root scope
A := op.Placeholder(root.SubScope("input"), tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 2)))
x := op.Placeholder(root.SubScope("input"), tf.Int64, op.PlaceholderShape(tf.MakeShape(2, 1)))
fmt.Println(A.Op.Name(), x.Op.Name())
~~~
正常編譯并運行:`go run attempt2.go`。結果如下:
~~~
input/Placeholder input_1/Placeholder
~~~
**問答時間:**
關于 Tensorflow 系統我們學到了什么?一個節點可由它被定義的作用域所區分。作用域就是從圖的根節點直到操作節點的路徑。有兩種方式可以定義執行相同操作的節點:在不同的作用域中定義操作(Go 的方式)或者改變操作名稱(Python 自動實現或者我們可以使用 C++ 做到)
我們剛剛解決了節點名稱重復的問題,另一個問題又出現了。
~~~
panic: failed to add operation "MatMul": Value for attr 'T' of int64 is not in the list of allowed values: half, float, double, int32, complex64, complex128
~~~
為什么`MatMul`節點定義會報錯?我們只是想讓兩個`tf.int64`矩陣相乘!看起來`int64`是`MatMul`唯一不能接受的參數類型。
> 屬性 ‘T’ 的取值 int64,不在允許的列表中:half,float,double,int32,complex32, complex64, complex128
這是什么列表?為什么我們可以將兩個`int32`類型的矩陣相乘卻不支持`int64`類型?
讓我們繼續研究這個問題,搞清楚到底發生了什么。
## [](https://log.zvz.im/2018/07/15/go-tensorflow/#%E7%AC%AC%E4%B8%89%E8%AF%BE%EF%BC%9ATensorflow-%E7%B1%BB%E5%9E%8B%E4%BD%93%E7%B3%BB "第三課:Tensorflow 類型體系")第三課:Tensorflow 類型體系
讓我們深入到 C++ 源碼中,看一下`MatMul`操作的函數聲明:
~~~
REGISTER_OP("MatMul")
.Input("a: T")
.Input("b: T")
.Output("product: T")
.Attr("transpose_a: bool = false")
.Attr("transpose_b: bool = false")
.Attr("T: {half, float, double, int32, complex64, complex128}")
.SetShapeFn(shape_inference::MatMulShape)
.Doc(R"doc(
Multiply the matrix "a" by the matrix "b".
The inputs must be two-dimensional matrices and the inner dimension of
"a" (after being transposed if transpose_a is true) must match the
outer dimension of "b" (after being transposed if transposed_b is
true).
*Note*: The default kernel implementation for MatMul on GPUs uses
cublas.
transpose_a: If true, "a" is transposed before multiplication.
transpose_b: If true, "b" is transposed before multiplication.
)doc");
~~~
這行代碼定義了`MatMul`操作的接口:特別注意,我們使用`REGISTER_OP`宏聲明了操作的:
* 名稱:`MatMul`
* 參數:`a`,`b`
* 屬性(可選參數):`transpose_a`,`transpose_b`
* 模板`T`支持的類型:`half, float, double, int32, complex64, complex128`
* 輸出形式:自動推理的
* 文檔
這個宏調用不包含任何 C++ 代碼,不過它告訴我們**當定義個一個操作時,即使它使用了模板,我們也必須指定對于指定類型(或屬性)`T`所支持的類型列表**。例如,屬性`.Attr("T: {half, float, double, int32, complex64, complex128}")`就限制了類型`T`必須是列表中的某一項。
我們可以從[教程](https://www.tensorflow.org/extend/adding_an_op)中看到,甚至在使用模板`T`的時候,我們也必須為每個支持的重載顯示地注冊到內核中。內核是以 CUAD 方式對 C/C++ 函數進行并行調用執行的。
`MatMul`的作者之所以決定只支持之前列出的參數類型,而不支持`int64`類型,可能有以下兩個原因:
1. 疏忽:這是有可能的,畢竟 Tensorflow 的代碼也是人寫的!
2. 為了支持那些不完全支持`int64`類型操作的設備,這樣內核的這些特定實現就不會到處都是,而導致在本可以支持的硬件上無法運行。
回到我們的報錯上來:修復的方法很明顯。我們必須要給`MatMul`方法傳遞它所支持的數據類型。
讓我們創建`attempt3.go`文件,將每一行用到`int64`的地方改成`int32`。
有件事要注意一下:**Go 語言的接口包定義了一套自有的類型,與 Go 原生類型基本上是 1:1 對應的關系。當我們向圖內填入參數時需要對照這個對應關系(比如,對于定義為`tf.Int32`的占位符要傳入`int32`類型的值)。從圖中讀取數據時也要準從相同的法則。**由張量計算返回的`*tf.Tensor`類型,自帶`Value()`方法,它可以返回一個`interface{}`類型的值,必須由我們去轉化為正確的類型(我們構建圖的時候可知此類型)。
正常執行`go run attempt3.go`。結果如下:
~~~
input/Placeholder input_1/Placeholder
[[210] [-210]]
~~~
棒極了!
這兒有一份完整的`attempt3`的代碼,你可以編譯并運行它。
~~~
package main
import (
"fmt"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
"github.com/tensorflow/tensorflow/tensorflow/go/op"
)
func main() {
// Let's describe what we want: create the graph
// We want to define two placeholder to fill at runtime
// the first placeholder A will be a [2, 2] tensor of integers
// the second placeholder x will be a [2, 1] tensor of intergers
// Then we want to compute Y = Ax
// Create the first node of the graph: an empty node, the root of our graph
root := op.NewScope()
// Define the 2 placeholders
// define 2 subscopes of the root subscopes, called "input". In this
// way we expect the have a input/ and a input_1/ scope under the root scope
A := op.Placeholder(root.SubScope("input"), tf.Int32, op.PlaceholderShape(tf.MakeShape(2, 2)))
x := op.Placeholder(root.SubScope("input"), tf.Int32, op.PlaceholderShape(tf.MakeShape(2, 1)))
fmt.Println(A.Op.Name(), x.Op.Name())
// Define the operation node that accepts A & x as inputs
product := op.MatMul(root, A, x)
// Every time we passed a `Scope` to an operation, we placed that operation **under**
// that scope.
// As you can see, we have an empty scope (created with NewScope): the empty scope
// is the root of our graph and thus we denote it with "/".
// Now we ask tensorflow to build the graph from our definition.
// The concrete graph is created from the "abstract" graph we defined using the combination
// of scope and op.
graph, err := root.Finalize()
if err != nil {
// It's useless trying to handle this error in any way:
// if we defined the graph wrongly we have to manually fix the definition.
// It's like a SQL query: if the query is not syntactically valid we have to rewrite it
panic(err.Error())
}
// If here: our graph is syntatically valid.
// We can now place it within a Session and execute it.
var sess *tf.Session
sess, err = tf.NewSession(graph, &tf.SessionOptions{})
if err != nil {
panic(err.Error())
}
// In order to use placeholders, we have to create the Tensors containing the values to feed into
// the network
var matrix, column *tf.Tensor
// A = [ [1, 2], [-1, -2] ]
if matrix, err = tf.NewTensor([2][2]int32{{1, 2}, {-1, -2}}); err != nil {
panic(err.Error())
}
// x = [ [10], [100] ]
if column, err = tf.NewTensor([2][1]int32{{10}, {100}}); err != nil {
panic(err.Error())
}
var results []*tf.Tensor
if results, err = sess.Run(map[tf.Output]*tf.Tensor{
A: matrix,
x: column,
}, []tf.Output{product}, nil); err != nil {
panic(err.Error())
}
for _, result := range results {
fmt.Println(result.Value().([][]int32))
}
}
~~~
**問答時間:**
關于 Tensorflow 系統我們學到了什么?每個操作都有它自己的關聯核心實現。Tensorflow 可以看作是一種強類型的描述性語言。它不僅要遵守 C++ 的類型規則,它還得在注冊操作時指定執行時使用數據的類型。
## [](https://log.zvz.im/2018/07/15/go-tensorflow/#%E6%80%BB%E7%BB%93 "總結")總結
使用 Go 語言定義圖并進行運算,帶給我們一次深入理解 Tensorflow 底層架構的機會。采取逐步試錯的方式,我們解決了這個簡單的問題,而且一步步學習到了關于圖,圖的節點以及類型體系的新知識。
翻譯自:[Understanding Tensorflow using Go](https://pgaleone.eu/tensorflow/go/2017/05/29/understanding-tensorflow-using-go/)
- 序言
- 目錄
- 環境搭建
- Linux搭建golang環境
- Windows搭建golang環境
- Mac搭建golang環境
- Go 環境變量
- 編輯器
- vs code
- Mac 安裝vs code
- Windows 安裝vs code
- vim編輯器
- 介紹
- 1.Go語言的主要特征
- 2.golang內置類型和函數
- 3.init函數和main函數
- 4.包
- 1.工作空間
- 2.源文件
- 3.包結構
- 4.文檔
- 5.編寫 Hello World
- 6.Go語言 “ _ ”(下劃線)
- 7.運算符
- 8.命令
- 類型
- 1.變量
- 2.常量
- 3.基本類型
- 1.基本類型介紹
- 2.字符串String
- 3.數組Array
- 4.類型轉換
- 4.引用類型
- 1.引用類型介紹
- 2.切片Slice
- 3.容器Map
- 4.管道Channel
- 5.指針
- 6.自定義類型Struct
- 流程控制
- 1.條件語句(if)
- 2.條件語句 (switch)
- 3.條件語句 (select)
- 4.循環語句 (for)
- 5.循環語句 (range)
- 6.循環控制Goto、Break、Continue
- 函數
- 1.函數定義
- 2.參數
- 3.返回值
- 4.匿名函數
- 5.閉包、遞歸
- 6.延遲調用 (defer)
- 7.異常處理
- 8.單元測試
- 壓力測試
- 方法
- 1.方法定義
- 2.匿名字段
- 3.方法集
- 4.表達式
- 5.自定義error
- 接口
- 1.接口定義
- 2.執行機制
- 3.接口轉換
- 4.接口技巧
- 面向對象特性
- 并發
- 1.并發介紹
- 2.Goroutine
- 3.Chan
- 4.WaitGroup
- 5.Context
- 應用
- 反射reflection
- 1.獲取基本類型
- 2.獲取結構體
- 3.Elem反射操作基本類型
- 4.反射調用結構體方法
- 5.Elem反射操作結構體
- 6.Elem反射獲取tag
- 7.應用
- json協議
- 1.結構體轉json
- 2.map轉json
- 3.int轉json
- 4.slice轉json
- 5.json反序列化為結構體
- 6.json反序列化為map
- 終端讀取
- 1.鍵盤(控制臺)輸入fmt
- 2.命令行參數os.Args
- 3.命令行參數flag
- 文件操作
- 1.文件創建
- 2.文件寫入
- 3.文件讀取
- 4.文件刪除
- 5.壓縮文件讀寫
- 6.判斷文件或文件夾是否存在
- 7.從一個文件拷貝到另一個文件
- 8.寫入內容到Excel
- 9.日志(log)文件
- server服務
- 1.服務端
- 2.客戶端
- 3.tcp獲取網頁數據
- 4.http初識-瀏覽器訪問服務器
- 5.客戶端訪問服務器
- 6.訪問延遲處理
- 7.form表單提交
- web模板
- 1.渲染終端
- 2.渲染瀏覽器
- 3.渲染存儲文件
- 4.自定義io.Writer渲染
- 5.模板語法
- 時間處理
- 1.格式化
- 2.運行時間
- 3.定時器
- 鎖機制
- 互斥鎖
- 讀寫鎖
- 性能比較
- sync.Map
- 原子操作
- 1.原子增(減)值
- 2.比較并交換
- 3.導入、導出、交換
- 加密解密
- 1.md5
- 2.base64
- 3.sha
- 4.hmac
- 常用算法
- 1.冒泡排序
- 2.選擇排序
- 3.快速排序
- 4.插入排序
- 5.睡眠排序
- 限流器
- 日志包
- 日志框架logrus
- 隨機數驗證碼
- 生成指定位數的隨機數
- 生成圖形驗證碼
- 編碼格式轉換
- UTF-8與GBK
- 解決中文亂碼
- 設計模式
- 創建型模式
- 單例模式
- singleton.go
- singleton_test.go
- 抽象工廠模式
- abstractfactory.go
- abstractfactory_test.go
- 工廠方法模式
- factorymethod.go
- factorymethod_test.go
- 原型模式
- prototype.go
- prototype_test.go
- 生成器模式
- builder.go
- builder_test.go
- 結構型模式
- 適配器模式
- adapter.go
- adapter_test.go
- 橋接模式
- bridge.go
- bridge_test.go
- 合成/組合模式
- composite.go
- composite_test.go
- 裝飾模式
- decoretor.go
- decorator_test.go
- 外觀模式
- facade.go
- facade_test.go
- 享元模式
- flyweight.go
- flyweight_test.go
- 代理模式
- proxy.go
- proxy_test.go
- 行為型模式
- 職責鏈模式
- chainofresponsibility.go
- chainofresponsibility_test.go
- 命令模式
- command.go
- command_test.go
- 解釋器模式
- interpreter.go
- interperter_test.go
- 迭代器模式
- iterator.go
- iterator_test.go
- 中介者模式
- mediator.go
- mediator_test.go
- 備忘錄模式
- memento.go
- memento_test.go
- 觀察者模式
- observer.go
- observer_test.go
- 狀態模式
- state.go
- state_test.go
- 策略模式
- strategy.go
- strategy_test.go
- 模板模式
- templatemethod.go
- templatemethod_test.go
- 訪問者模式
- visitor.go
- visitor_test.go
- 數據庫操作
- golang操作MySQL
- 1.mysql使用
- 2.insert操作
- 3.select 操作
- 4.update 操作
- 5.delete 操作
- 6.MySQL事務
- golang操作Redis
- 1.redis介紹
- 2.golang鏈接redis
- 3.String類型 Set、Get操作
- 4.String 批量操作
- 5.設置過期時間
- 6.list隊列操作
- 7.Hash表
- 8.Redis連接池
- 其它Redis包
- go-redis/redis包
- 安裝介紹
- String 操作
- List操作
- Set操作
- Hash操作
- golang操作ETCD
- 1.etcd介紹
- 2.鏈接etcd
- 3.etcd存取
- 4.etcd監聽Watch
- golang操作kafka
- 1.kafka介紹
- 2.寫入kafka
- 3.kafka消費
- golang操作ElasticSearch
- 1.ElasticSearch介紹
- 2.kibana介紹
- 3.寫入ElasticSearch
- NSQ
- 安裝
- 生產者
- 消費者
- zookeeper
- 基本操作測試
- 簡單的分布式server
- Zookeeper命令行使用
- GORM
- gorm介紹
- gorm查詢
- gorm更新
- gorm刪除
- gorm錯誤處理
- gorm事務
- sql構建
- gorm 用法介紹
- Go操作memcached
- beego框架
- 1.beego框架環境搭建
- 2.參數配置
- 1.默認參數
- 2.自定義配置
- 3.config包使用
- 3.路由設置
- 1.自動匹配
- 2.固定路由
- 3.正則路由
- 4.注解路由
- 5.namespace
- 4.多種數據格式輸出
- 1.直接輸出字符串
- 2.模板數據輸出
- 3.json格式數據輸出
- 4.xml格式數據輸出
- 5.jsonp調用
- 5.模板處理
- 1.模板語法
- 2.基本函數
- 3.模板函數
- 6.請求處理
- 1.GET請求
- 2.POST請求
- 3.文件上傳
- 7.表單驗證
- 1.表單驗證
- 2.定制錯誤信息
- 3.struct tag 驗證
- 4.XSRF過濾
- 8.靜態文件處理
- 1.layout設計
- 9.日志處理
- 1.日志處理
- 2.logs 模塊
- 10.會話控制
- 1.會話控制
- 2.session 包使用
- 11.ORM 使用
- 1.鏈接數據庫
- 2. CRUD 操作
- 3.原生 SQL 操作
- 4.構造查詢
- 5.事務處理
- 6.自動建表
- 12.beego 驗證碼
- 1.驗證碼插件
- 2.驗證碼使用
- beego admin
- 1.admin安裝
- 2.admin開發
- beego 熱升級
- beego實現https
- gin框架
- 安裝使用
- 路由設置
- 模板處理
- 文件上傳
- gin框架中文文檔
- gin錯誤總結
- 項目
- 秒殺項目
- 日志收集
- 面試題
- 面試題一
- 面試題二
- 錯題集
- Go語言陷阱和常見錯誤
- 常見語法錯誤
- 初級
- 中級
- 高級
- Go高級應用
- goim
- goim 啟動流程
- goim 工作流程
- goim 結構體
- gopush
- gopush工作流程
- gopush啟動流程
- gopush業務流程
- gopush應用
- gopush新添功能
- gopush壓力測試
- 壓測注意事項
- rpc
- HTTP RPC
- TCP RPC
- JSON RPC
- 常見RPC開源框架
- pprof
- pprof介紹
- pprof應用
- 使用pprof及Go 程序的性能優化
- 封裝 websocket
- cgo
- Golang GC
- 查看程序運行過程中的GC信息
- 定位gc問題所在
- Go語言 demo
- 用Go語言計算一個人的年齡,生肖,星座
- 超簡易Go語言實現的留言板代碼
- 信號處理模塊,可用于在線加載配置,配置動態加載的信號為SIGHUP
- 陽歷和陰歷相互轉化的工具類 golang版本
- 錯誤總結
- 網絡編程
- 網絡編程http
- 網絡編程tcp
- Http請求
- Go語言必知的90個知識點
- 第三方庫應用
- cli應用
- Cobra
- 圖表庫
- go-echarts
- 開源IM
- im_service
- 機器學習庫
- Tensorflow
- 生成二維碼
- skip2/go-qrcode生成二維碼
- boombuler/barcode生成二維碼
- tuotoo/qrcode識別二維碼
- 日志庫
- 定時任務
- robfig/cron
- jasonlvhit/gocron
- 拼多多開放平臺 SDK
- Go編譯
- 跨平臺交叉編譯
- 一問一答
- 一問一答(一)
- 為什么 Go 標準庫中有些函數只有簽名,沒有函數體?
- Go開發的應用
- etcd
- k8s
- Caddy
- nsq
- Docker
- web框架