入门Golang


最近抽空了解了下Golang这门语言,它是一种由Google开发的编程语言。总结下来,优势是小而简单,编译快,性能与C媲美;也终于清楚了协程的概念。类似的多语言的学习可以巩固一些相似的语言特性,也可以带来新的编程视角。这篇文章主要是对Golang的语言特性做一个系统的梳理,增强记忆。

introduction about Go design from Wiki:

Go is influenced by C (especially the Plan 9 dialect[47][failed verification – see discussion]), but with an emphasis on greater
simplicity and safety. It consists of:

  • A syntax and environment adopting patterns more common in dynamic languages:
    – Optional concise variable declaration and initialization through type inference (x := 0 instead of int x = 0; or var x = 0;)
    – Fast compilation
    – Remote package management (go get) and online package documentation
  • Distinctive approaches to particular problems:
    – Built-in concurrency primitives: light-weight processes (goroutines), channels, and the select statement
    – An interface system in place of virtual inheritance, and type embedding instead of non-virtual inheritance
    – A toolchain that, by default, produces statically linked native binaries without external Go dependencies
  • A desire to keep the language specification simple enough to hold in a programmer’s head,[52] in part by omitting features that are common in similar languages.

1. 语言特性

1.1 控制结构

if : 可以在表达式前执行一个简单的语句

if v := x - 100; v < 0 {
  return v
}

switch : 注意空分支 与 fallthrough的不同,不需要用break来明确退出一个case
for-range: 遍历数组、切片、字符串、Map 等

1.2 常用数据结构

1.2.1 常量

const a = "this is const"

1.2.2 类型重命名

type ServiceType string

const (
	ServiceTypeClusterIP ServiceType    = "ClusterIP"
	ServiceTypeNodePort ServiceType     = "NodePort"
	ServiceTypeLoadBalance rServiceType = "LoadBalancer"
	ServiceTypeExternalName ServiceType = "ExternalName"
)

1.2.3 变量

var 可用于声明一个变量列表,跟函数的参数列表一样,类型在最后;
默认有初始化值
如果初始化时就赋值,则不需要var修饰,需要 “:=” 符号

var b = "this is variable"
var c, b, a bool
var i, j int = 2, 3
//类型转换
var i int = 2
var f float64 = float64(i)
var u uint = uint(f)
//或者
i := 42
f := float64(i)
u := uint(f)
//类型推导
var i int
j := i //j也是一个int

1.2.4 匿名变量

用 _ 来表示,作用就是可以避免创建定义一些无意义的变量,不会分配内存。

1.2.5 指针变量

类型指针和数组切片
作为类型指针时,允许对这个指针类型的数据进行修改指向其它内存地址,传递数据时如果使用指针则无须拷贝数据从而节省内存空间,此外和C语言中的指针不同,Go语言中的类型指针不能进行偏移和运算,因此更为安全。

1.2.6 数组

相同类型且长度固定连续内存片段,以编号访问每个元素
定义方法:var identifier [len]type

var a = [3]int{1, 2, 3} // 声明时初始化
var b = new([3]string)   // 通过 new 初始化
var c = make([]string, 3) // 通过 make初始化

1.2.7 切片

切片是对数组一个连续片段的引用,在未初始化前默认为nil, 长度为0
数组定义中不指定长度即为切片
定义方法:var identifier []type

myArray := [5]int{1, 2, 3, 4, 5}
mySlice := myArray[1:3]
fmt.Printf("mySlice %+v\n", mySlice)
fullSlice := myArray[:]
//new返回指针地址
//make返回第一个元素,可预设内存空间,避免未来的内存拷贝
mySlice1 := new([]int)    \\&[]
mySlice2 := make([]int, 0)  \\[]
mySlice3 := make([]int, 10)  \\ [0 0 0 0 0 0 0 0 0 0]
mySlice4 := make([]int, 10, 20)\\ [0 0 0 0 0 0 0 0 0 0]

1.2.8 Map

声明方法:var map1 map[keyType][valueType]

myMap := make(map[string]string, 10)
myMap["a"] = "b"
//函数作为map的value
myFuncMap := map[string]func() int{
	"funcA": func() int {
		return 1
	},
}
fmt.Println(myFuncMap)
f := myFuncMap["funcA"]
fmt.Println(f())

访问map元素
按key取值

value, exists := myMap["a"]
if exists {
    println(value)
}

遍历map

for k, v := range myMap {
    println(k, v)
}

1.2.9 结构体和指针 & 反射

没有class,类是用结构体来定义的
定义方法: type identifier struct

package main

import (
	"encoding/json"
	"fmt"
	"reflect"
)

type MyType struct {
	Name string `json:"name"`
	Address
}

type Address struct {
	City string `json:"city"`
}

func main() {
	mt := MyType{Name: "test", Address: Address{City: "shanghai"}}
	b, _ := json.Marshal(&mt)
	fmt.Println(string(b))
	myType := reflect.TypeOf(mt)
	name := myType.Field(0)
	tag := name.Tag.Get("json")
	println(tag)
	tb := TypeB{P2: "p2", TypeA: TypeA{P1: "p1"}}
	println(tb.P1)
}

type TypeA struct {
	P1 string
}
type TypeB struct {
	P2 string
	TypeA
}

1.3 函数

main函数 如何传入参数
方法1:

fmt.Println("os args is:", os.Args)

方法2:

name := flag.String("name", "world", "specify the name you want to say hi")
flag.Parse()

init函数
在包初始化时运行,且仅运行一次
在这里插入图片描述

1.3.1 返回值

函数可以返回任意数量的返回值
命名返回值,被视作定义在函数顶部的变量
调用者用“占位符_”忽略部分返回值

result, _ = strconv.Atoi(origStr)

go也可以传递变长参数

1.3.2 回调函数

package main

func main() {
	func() {
		println("hello world")
	}()
	DoOperation(1, increase)
}

func DoOperation(y int, f func(int, int)) {
	f(y, 1)
}

func increase(a, b int) {
	println("increase result is:", a+b)
}

1.3.3 闭包(匿名函数)

不能独立存在
可以赋值给其他变量
可以直接调用

func(x,y int){println(x+y)}(1,2)

可以作为函数返回值

func Add()(func(b int) int)

1.4 方法

作用在接收者上的函数
定义方法:func(recv receiver_type) methodName(parameter_list)(return_value_list)

1.5 接口

package main

import "fmt"

type IF interface {
	getName() string
}

type Human struct {
	firstName, lastName string
}

type Plane struct {
	vendor string
	model  string
}

func (h *Human) getName() string {
	return h.firstName + ", " + h.lastName
}

func (p Plane) getName() string {
	return fmt.Sprintf("vendor: %s, model: %s", p.vendor, p.model)
}

type Car struct {
	factory, model string
}

func (c *Car) getName() string {
	return c.factory + "-" + c.model
}

func main() {
	interfaces := []IF{}
	h := new(Human)//struct初始化意味着空间分配,对struct的引用不会出现空指针
	h.firstName = "first"
	h.lastName = "last"
	interfaces = append(interfaces, h)
	c := new(Car)
	c.factory = "benz"
	c.model = "s"
	interfaces = append(interfaces, c)
	for _, f := range interfaces {
		fmt.Println(f.getName())
	}
	p := Plane{}
	p.vendor = "testVendor"
	p.model = "testModel"
	fmt.Println(p.getName())
}

1.6 panic 、defer和recover

函数返回前执行某个语句或函数,等同于java中的finally
常见使用场景:关闭资源

package main

import "fmt"

func main() {
	defer func() {
		fmt.Sprintln("defer func is called")
		if err := recover(); err != nil {
			fmt.Printf("it is defer func: %+v\n\n", err)
		}
	}()
	panic("a panic is triggered")
}

2. CSP

commmunicating sequential process
描述两个独立的并发实体通过共享通讯channel进行通讯的并发模型

2.1 GMP模型

在这里插入图片描述

Goroutine
goroutine不是操作系统线程,而是将一个操作系统线程分段使用,通过调度器实现协作式调度的用户态线程。
每个goroutine都有自己的栈空间,定时器,初始化的栈空间2k左右,空间会随着需求增长
Machine
代表内核线程,记录内核线程信息, M从P上获得goroutine并执行
Process
调度器,负责调度goroutine,维护一个本地goroutine队列

2.2 通道 channel

用于协程之间通讯是同步的。协程之间虽然解耦,但是他们和Channel有着耦合。

  • 一端发送数据,一端接受数据
  • 同一时间只有一个协程可以访问数据,无共享内存模式可能出现的内存竞争
    当缓冲区满,数据发送阻塞
    make关键字创建通道可定义缓冲区容量,默认缓冲区容量为0
ch := make(chan int)
go func() {
    fmt.Println("hello from goroutine")
    ch <- 0 //数据写入Channel
}()
i := <- ch //从Channel中取数据并赋值,读不到数据,会一直阻塞在这里,直到有数据写入到Channel

//下面两种定义的区别
ch1 := make(chan int)  //缓冲区大小为0
ch2 := make(chan int, 10)//缓冲区大小为10,向channel中放入的第11个数据会阻塞;

//遍历Channel缓冲区
ch := make(chan int, 10)
go func() {
    for i := 0; i < 10; i++ {
        rand.Seed(time.Now().UnixNano())
        n := rand.Intn(10) //n will be between 0 and 10
        fmt.Println("putting:", n)
        ch <- n
    }
    close(ch)
}()
fmt.Println("hello from main")
for v := range ch {
    fmt.Println("receiving:", v)
}

单向通道

var c = make(chan int)
go prod(c)
go consume(c)
// 只发送通道
func prod(ch chan<- int) {
	for {
		ch <- 1
	}
}

// 只接收通道
func consume(ch <-chan int) {
	for {
		<-ch
	}
}

关闭通道
通道无需每次关闭,关闭的作用是告诉接收者该通道再无新数据发送,只有发送发需要关闭通道

close(chan)

当多个协程同时运行时,可通过select轮询多个通道

  • 若所有通道都阻塞,则等待;定义了default,则执行default;
  • 若多个通道就绪,随机选择;
select {
    case v := <- ch1:
        ...
    case v := <- ch2:
        ....
    default:
        ....
}

2.3 定时器 Timer

使用场景: 为协程设定超时时间

ch := make(chan int)
go func() {
	//ch <- 1
}()
timer := time.NewTimer(2 * time.Second) //为协程设定超时时间
fmt.Println("put into ch")
select {
case <-ch: //check normal channel
	fmt.Println("received from ch")
case <-timer.C:
	fmt.Println("timeout waiting from channel ch")
}

2.4 如何停止子协程

方法一:done channel

done := make(chan bool)
go func() {
	for {
		select {
		case <-done:
			fmt.Println("done channel is triggered, exit child go routine")
			return
		}
	}
}()
close(done)

方法二:基于Context

package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	baseCtx := context.Background() //创建context
	ctx := context.WithValue(baseCtx, "key_k1", "value_v1") //向context添加键值对
	go func(c context.Context) {
		fmt.Println(c.Value("key_k1")) //context.Value("key") ==> 通过key获取value;实现了协程间的变量传递
	}(ctx)
	timeoutCtx, cancel := context.WithTimeout(baseCtx, time.Second) //设置timeout为1秒
	defer cancel()
	go func(ctx context.Context) {
		ticker := time.NewTicker(1 * time.Second) //节拍器,每秒执行一次
		for _ = range ticker.C {//每秒做一次for循环
			select {
			case <-ctx.Done()://context的timeout为一秒,timeout后Done()则有数据
				fmt.Println("child process interrupt...")
				return
			default:
				fmt.Println("enter default")
			}
		}
	}(timeoutCtx)
	select {
	case <-timeoutCtx.Done():
		time.Sleep(1 * time.Second)
		fmt.Println("main process exit!")
	}
}

除此之外,还要注意下述知识的与java的对比理解:
锁的类型及作用
多个协程之间协作
线程 & 协程调度
内存管理
GC

  • 27
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值