基础
[TOC]
特性
- Go 并发编程
- 采用CSP模型
- 不需要锁,不需要callback
- 并发编程 vs 并行计算
安装Go环境
- 安装编译器 https://studygolang.com/dl
- 设置环境变量 GO_HOME,PATH
- idea 安装 go 插件
Go 命令
命令格式
go command [arguments]
编译(生成一个exe文件)
go build HelloWorld.go
获取第三方库
# 1.
go get url
# 2. 下载七牛开发的gopm包管理工具,方便国内环境下载
go get -u github.com/gpmgo/gopm
Go 学习工具
go tool tour
检查数据并发冲突
func main(){
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ { //write i
wg.Add(1)
go func() {
fmt.Println(i) //read i
wg.Done()
}()
}
wg.Wait()
fmt.Println("main over.")
}
# 检查数据并发冲突
go run -race xxx.go
测试
- 表格测试 (测试数据与算法分离)
- http 测试
性能
文档
// 启动内置的文档web服务
godoc -http :8888
内置HTTP Server
import (
"net/http"
"fmt"
)
func main(){
//路由映射
http.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) {
//获取http 元数据
agent := request.UserAgent()
//获取name参数值
name := request.FormValue("name")
//输出给客户端
fmt.Fprintf(writer,"agent:[%s]",agent)
fmt.Fprintf(writer,"msg:[Hello %s]",name)
})
//启动server,并监听8888端口
http.ListenAndServe("localhost:8888", nil)
}
语法特性
"*"
与 "&"
"*"
作用
- 指针变量声明
- 指针取值
"&"
作用
- 值取指针
包
- 一个目录相当于一个包,包名建议与目录名一致
- main 方法只能在 main 包中
变量
类型显示转换
int64(52)
变量声明赋值
a := 1 //声明并赋值,只能用于函数内部
var a int = 1 //可在函数外部定义变量
var a, b = 1, "b"
const (
a=1
b="b"
)
变量值替换
a := "a"
b := "b"
a,b = b,a
fmt.Println("a=",a,",b=",b)
interface{}
相当与 Java 的 Object
类型断言
针对于
interface{}
类型的变量
/**类型断言**/
val := interface{}(123) //转为interface{},否则无法断言
fmt.Printf("Type=%T,Value=%v\n",val,val)
//v,c := val.(int) //如果断言类型与val原类型不一致,v为断言类型的默认值,c=false;否则v=val,c=true
v,c := val.(int64) //如果断言类型与val原类型不一致,v为断言类型的默认值,c=false;否则v=val,c=true
if !c {fmt.Printf("类型断言失败,val 不是 %T 类型\n",v)}
fmt.Printf("Type=%T,Value=%v\n",v,v)
/**类型转换**/
vv := int64(v)
fmt.Printf("Type=%T,Value=%v\n",vv,vv)
创建
- 内置函数
- new (适用与任何类型,用于分配内存,返回一个0值指针)
- make (适用与
slice
,map
,chan
, 用于内存分配,初始化,返回一个值)
控制结构
for 替代 while
if-else 分支共享变量
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} //创建底层数组和slice
for _, v := range arr { // _表示忽略index,v为元素值
//if-else每个分支都能访问到 val 变量
if val := map[string]string{"even": "偶数", "odd": "奇数"}; v%2 == 0 {
fmt.Println(v, val["even"])
} else {
fmt.Println(v, val["odd"])
}
}
swith 代替 if-elseif-else
找到第一个匹配的 case 后自动break
level :=1
var scope int
//1
switch level {
case 1: scope=100
case 2: scope=90
case 3: scope=80
case 4: scope=60
default:
scope=0
}
//2
switch {
case scope >= 60:
fmt.Println("及格")
case scope >= 80:
fmt.Println("良好")
case scope >= 95:
fmt.Println("优秀")
default:
fmt.Println("不及格")
}
//3
var x interface{}
switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:
fmt.Printf("x 是 bool 或 string 型" )
default:
fmt.Printf("未知型")
}
函数
defer 栈
- 类比 java finallly
defer func(){err := recover()}()
//捕获处理panic抛出的error
func fn(){
//在fn函数结束前打印出 "hello world"
defer fmt.Print("world")
...
defer fmt.Print("hello")
...
}
函数闭包
- 所谓闭包是指内层函数引用了外层函数中的变量或称为引用了自由变量的函数,其返回值也是一个函数
- 闭包可以用来对一个函数进行 功能增强(类似于aop)
func fib() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
func main() {
fn := fib() //执行fib(),返回匿名函数给fn,a,b变量作用域扩展到main
// Function calls are evaluated left-to-right.
for i := 0; i < 10; i++ {
fmt.Print(fn() , " ")
}
}
支持多个函数返回值
func main(){
i,j := fn() //获取两个返回值
m,_ := fn() //只获取第一个返回值,忽略第二个
_,n := fn() //忽略第一个,获取第二个
fmt.Print(i,j,m,n)
}
func fn() (int, string) {
a := 1
b := "b"
return a, b
}
面向对象
封装
- 结构体与方法
func main(){
p := Point{11.11, 22.22}
fmt.Println("old point=", p)
xy_Map := p.ToMap()
xy_Map["x"] += xy_Map["y"] //x=x+y
//获取p指针赋给pp
pp := &p
pp.FromMap(xy_Map)
fmt.Println("new point=", p)
}
type Point struct {
X, Y float64
}
//定义Point方法
func (p Point) ToMap() map[string]float64 {
return map[string]float64{"x": p.X, "y": p.Y}
}
//定义Point*方法
func (p *Point) FromMap(xy_map map[string]float64) {
p.X = xy_map["x"]
p.Y = xy_map["y"]
}
继承(假)
- 使用匿名组合实现继承
- 可继承方法
- 可扩展方法
- 可继承属性,不过在初始化属性值时还是需要传递
- 可扩展属性
- 使用别名1实现继承
- 不可继承方法,可通过强转为源类型调用方法(应为Go 必须显式强转)
- 可扩展方法
- 可继承属性
- 不可扩展属性(- 与真继承不一样的地方)
- 别名1 和别名2的区别:
- 别名1
type AA A
中 AA 是在 A的基础上的新类型- 别名2 (v1.9 新特性)
type AAA = A
- AAA 就是 A,真别名,不能用来实现继承
- 对于内建类型A,AAA不能扩展方法,否则会破坏整个类型;对于 自定义类型 A 时,AAA扩展方法===A扩展方法
- 由于 Go 是假继承,所以没有 Java 中抽象函数,类多态,所以 Java 中的一些涉及到继承的设计模式在 Go 也是无法实现的;例如:模板模式
- struct 嵌套 (组合)
func main() {
lang := &Go{Lang:&Lang{Author:"Ken",Version:"v1.10"},Oop:true,Fp:true}
fmt.Println(lang)
//“匿名组合特点,可直接获取组合Lang的属性”
// 等价于 fmt.Println(lang.Lang.Author)
fmt.Println(lang.Author)
fmt.Printf("%T supports oop: %v",lang,lang.Oop)
fmt.Printf("%T supports Fp: %v",lang,lang.Fp)
}
type Lang struct {
Author string
Version string
}
func (this *Lang) String() string {
return fmt.Sprintf("%T is a designed by %s,current version is %s", this, this.Author, this.Version)
}
//"匿名组合"
type Go struct {
*Lang //此处没有变量名,此种方式是“匿名组合”
Oop bool //是否支持面向对象
Fp bool //是否支持函数式
}
- 别名 1
func main() {
lang := &Go{Author:"Ken",Version:"v1.10"}
fmt.Println(lang) //不会去调用func (this *Lang) String() string,说明别名调用不了原类型的方法,只是继承了属性
fmt.Println((*Lang)(lang)) //如果需要去调用源类型方法,需要类型强转
fmt.Println(lang.GetVersion()) //访问扩展方法
}
type Lang struct {
Author string
Version string
}
func (this *Lang) String() string {
return fmt.Sprintf("%T is a designed by %s,current version is %s", this, this.Author, this.Version)
}
//别名
type Go Lang
//扩展方法
func (this *Go) GetVersion() string{
return this.Version
}
- 别名2 (v1.9 新特性)
func main() {
aa := AA{"aa"} //AA("a")==A("a")
fmt.Println(aa.a())
a := A{"a"}
fmt.Println(a.a())
}
type A struct {
A string
}
// AA 真别名 A
type AA = A
//这里 (this AA) === (this A)
func (this AA) a() string{
return this.A
}
面向接口
接口与多态
- 只要结构体的方法签名和接口方法签名一致,就代表实现了该接口,无需显式指定
//声明一个接口
type Runable interface{
run()
}
接口作为参数实现多态时,这个参数为指针
func main(){
/** 直接创建struct,返回值 **/
car := Car{"GTR"}
machine := Machine{"空调"}
Run(&car)
Run(&machine)
Run(&car)
Run(&machine)
/** new(Type) 创建返回指针 **/
car2 := new(Car)
car2.Brand="GTR"
machine2 := new(Machine)
machine2.Name="空调"
Run(car2)
Run(machine2)
Run(car2)
Run(machine2)
}
/** 多态,参数runnable必须为指针 **/
func Run(runnable Runnable) {
runnable.Run()
}
type Machine struct {
Name string
}
func (m *Machine) Run() { //Machine implements Runnable
fmt.Printf("%T %s run.\n", m, m.Name)
m.Name="洗衣机"
}
type Car struct {
Brand string
}
func (c *Car) Run() { //Car implements Runnable
fmt.Printf("%T %s run.\n", c, c.Brand)
c.Brand="BMW"
}
枚举
Go中没有枚举,只是使用
constant
常量来代替
iota
的含义是:每当声明一个const
常量,iota
就会自增1,默认值为 0
type Season int
const (
SPRING Season = iota //0
SUMMER //1
FALL //2
_ //3
WINTER //4
)
func main() {
fmt.Println("Here is main.")
fmt.Println(SPRING,SUMMER,FALL,WINTER)
}
并发与goroutine
goroutine
- n:1 -thread
thread
调度goroutine
- 调度上下文数据结构
P
,存在一个可被调度运行的RunnableQueue
,当go func()
的时候,会构建一个gorounine
并进入RunnbaleQueue
, 随后 go 调度线程会从这个队列中取一个放在Thread
中执行- 当
gorountine
在运行时对chan
操作遇到阻塞,当前goroutine
不再存在与RunnableQueue
中,同时构建sudog
数据结构来存储gorountine
的现场并作为这个gorountine
的代理被hchan
的recvq/sendq
指向;- 当
chan
数据非空/非满时,会将recvq/sendq
指向的sudog
对应的gorountine
重新放入RunnableQueue
中;
go 关键字
go func() //开启一个goroutine 执行函数
chan 通道
关闭chan
- 同一个goroutine 重复
close(ch)
会panic- 写:panic
- 读:直到chan 中无数据时,第二个返回值为false
超时
select {case data:=<-ch: case <- time.After(3*time.Second):return false}
- 用于
goroutine
间通信的共享数据结构;make
函数在创建channel
的时候会在该进程的heap
区申请一块内存,创建一个hchan
结构体,返回执行该内存的指针,所以获取的的ch
变量本身就是一个指针,在函数之间传递的时候是同一个channel
;hchan
结构体使用一个环形队列来保存groutine
之间传递的数据(如果是缓存channel的话),使用两个list保存像该chan
发送和从该chan
接收数据的goroutine
,还有一个mutex
来保证操作这些结构的安全 ;
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
详见
(http://www.iigrowing.cn/bing_fa_zhi_tong_threadgo_yu_yan_de_goroutineakka_de_actor.html)
(https://blog.csdn.net/kongdefei5000/article/details/75209005)
- e.g. chan 接口多态
func main() {
ch := make(chan A)
wg := &sync.WaitGroup{}
wg.Add(2)
go func() {
a := <-ch
a.run()
a = <-ch
a.run()
wg.Done()
}()
go func() {
b := B{}
c := C{}
ch <- &b
ch <- &c
wg.Done()
}()
wg.Wait()
}
type A interface {
run()
}
type B struct {
}
func (this *B) run() {
log.Println("B run.")
}
type C struct {
}
func (this *C) run() {
log.Println("C run.")
}
对Unix pipeline 的仿真
func pipe(app1 func(in io.Reader,out io.Writer),
app2 func(in io.Reader,out io.Writer)
) func(in io.Reader,out io.Writer) {
return func(in io.Reader,out io.Writer){
pr,pw := io.Pipe()
defer pw.Close()
go func(){
defer pr.Close()
app2(pr,out)
}()
app1(in,pw)
}
}
func pipe(apps... func(in io.Reader,out io.Writer))
func(in io.Reader,out io.Writer){
app := apps[0]
len := len(apps)
for i:=1;i
Note
- 尽量用指针,指针的默认值为nil,如果是结构体则是一个字段全为默认值的struct,不利于 if 判断
- Go 没有Java 在使用匿名函数访问外部自由变量的时候,必须为final的安全限制
- chan 其中一方没相应会阻塞
- 使用 select 在一些情况下可避免,chan 造成循环等待
- 单例: Go 没有构造方法,防止外部直接strcut{},struct 应定义为私有(首字母小写),所以 sync.Once.Do{} 写单例时,应写在函数内,不是自身方法内(除非工厂类的方法)