Channel主要被用来在多个Goroutione之间传递数据,并且还会保证其过程的同步。总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序
Go并发模型 多线程共享内存。其实就是Java或者C++等语言中的多线程开发 CSP(communicating sequential processes)并发模型。Go语言特有 CSP并发模型是在1970年左右提出的概念,属于比较新的概念,不同于传统的多线程通过共享内存来通信,CSP讲究的是'‘以通信的方式来共享内存"。
请记住下面这句话: DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING. “不要以共享内存的方式来通信,相反,要通过通信来共享内存。”
普通的线程并发模型,像Java、C++、Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来限制,因此,在很多时候,衍生出一种方便操作的数据结构,叫做"线程安全的数据结构"。例如Java提供的包"java.util.concurrent"中的数据结构。Go中也实现了传统的线程并发模型
Go的CSP并发模型,是通过goroutine和channel来实现的。
goroutine 是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的"线程"类似,可以理解为"线程" channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的"管道",有点类似于Linux中的管道 开启一个goroutine的方式非常简单
go func() 通信机制channel也很方便,传数据用channel <- data,取数据用<-channel。在通信过程中,传数据channel <- data和取数据<-channel必然会成对出现,因为这边传,那边取,两个goroutine之间才会实现通信。而且不管传还是取,必阻塞,直到另外的goroutine传或者取为止。
import ( "time" "fmt" ) func worker(id int, c chan int) { // for true { // n, ok := <- c // if !ok { // break // } // fmt.
[Read More]
并发-Goroutine(协程)
Go学习笔记
轻量级"线程"
线程(Thread):有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程的切换一般也由操作系统调度
协程(Coroutine):又称微线程与子例程(或者称为函数)一样,协程也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛
和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制。它避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂
Goroutine和其他语言的协程(Coroutine)在使用方式上类似,但从字面意义上来看不同(一个是Goroutine,一个是Coroutine),再就是协程是一种协作任务控制机制,在最简单的意义上,协程不是并发的,而Go语言层面支持并发(Goroutine)。因此Goroutine可以理解为一种Go语言的协程。同时它可以运行在一个或多个线程上
Goroutine与创建线程相比,创建成本和开销都很小,每个goroutine的堆栈只有几kb,并且堆栈可根据程序的需要增长和缩小(线程的堆栈需指明和固定),所以go程序从语言层面支持了高并发
Goroutine是Go中最基本的执行单元。事实上每一个Go程序至少有一个Goroutine:主Goroutine
其它语言支持
C++:Boost.Coroutine Java:不支持(有第三方库) Python:使用yield关键字实现协程,3.5加入async def对协程原生支持 非抢占式多任务处理,由协程主动交出控制权
编译器/解释器/虚拟机层面的多任务
多个协程可能在一个或多个线程上运行
简单示例 import ( "fmt" "time" "runtime" ) func main() { var a [10]int for j := 0; j < 10 ; j++ { go func(j int) { for{ a[j]++ runtime.Gosched() //程序主动交出控制权 } }(j) } time.Sleep(time.Microsecond) fmt.Println(a) } 可以使用如下命令检测数据访问的冲突
[Read More]
性能调优-PProf
Go学习笔记
PProf pprof 是用于可视化和分析性能分析数据的工具,以 profile.proto 读取分析样本的集合,并生成报告以可视化并帮助分析数据(支持文本和图形报告)
profile.proto 是一个 Protocol Buffer v3 的描述文件,它描述了一组 callstack 和 symbolization 信息, 作用是表示统计分析的一组采样的调用栈,是很常见的 stacktrace 配置文件格式
涉及的工具包有:
runtime/pprof:采集程序(非 Server)的运行数据进行分析
net/http/pprof:采集 HTTP Server 的运行时数据进行分析(只是使用runtime/pprof包来进行封装,并在http端口上暴露出来)
#web服务器: import ( "net/http" _ "net/http/pprof" ) #一般应用程序(实际应用无web交互) import ( "net/http" _ "runtime/pprof" ) 使用工具命令bench在源文件目录生成性能采样文件,然后通过pprof工具交互式分析采样文件
go test -bench . -cpuprofile cpu.pprof ## 运行如下会出现交互式命令窗口,可以通过输入help命令获取帮助 ## 最简单的方式是输入web,会生成一张svg格式的树形结构图(需要安装Graphviz工具) go tool pprof cpu.pprof (pprof) web 采样范围
CPU Profiling:采集程序的 CPU 使用情况,按照一定的频率采集所监听的应用程序 CPU(含寄存器)的使用情况,可确定应用程序在主动消耗 CPU 周期时花费时间的位置 Memory Profiling:采集程序内存使用情况,在应用程序进行堆分配时记录堆栈跟踪,用于监视当前和历史内存使用情况,以及检查内存泄漏 Block Profiling:阻塞分析,记录 goroutine 阻塞等待同步(包括定时器通道)的位置,可以用来分析和查找死锁等性能瓶颈 Mutex Profiling:互斥锁分析,报告互斥锁的竞争情况 支持使用模式
[Read More]
调试与测试
Go学习笔记
传统测试 测试数据与测试逻辑混在一起
出错信息不明确
一旦一个数据出错,测试全部结束
@Test public void testAdd() { assertEquals(3, add(1, 2)); assertEquals(2, add(0, 2)); assertEquals(0, add(0, 0)); assertEquals(0, add(-1, 1)); assertEquals(Integer.MIN_VALUE, add(1, Integer.MAX_VALUE)); } 表格驱动测试 测试数据与测试逻辑分离
明确的出错信息(自定义出错信息内容)
可以部分失败
func calcTriangle(a, b int) int { var c int c = int(math.Sqrt(float64(a*a + b*b))) return c } tests := []struct{ a, b, c int }{ {3, 4, 5}, {5, 12, 13}, {8, 15, 17}, {12, 35, 37}, {30000, 40000, 50000}, } for _, tt := range tests { if actual := calcTriangle(tt.
[Read More]
资源管理与错误处理
Go学习笔记
Defer 调用特点
确保调用在函数结束时发生
defer列表为后进先出,参数在defer语句时才计算
func tryDefer() { defer fmt.Println(1) defer fmt.Println(2) fmt.Println(3) //return panic("error occurred") fmt.Println(4) } --------- 3 2 1 常见使用defer调用场景
Open/Close Lock/Unlock PrintHeader/PrintFooter 错误处理 常用错误处理
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666) if err != nil { if pathError, ok := err.(*os.PathError); !ok { panic(err) } else { fmt.Printf("%s, %s, %s\n", pathError.Op, pathError.Path, pathError.Err) } return } defer file.
[Read More]
函数式编程
Go学习笔记
“正统"函数式编程 不可变性:不能有状态,只有常量和函数
函数只能有一个参数
// 正统函数式编程 type iAdder func(int) (int, iAdder) func adder(base int) iAdder { return func(v int) (int, iAdder) { return base + v, adder(base + v) } } Go 函数式编程 函数是一等公民:参数,变量,返回值都可以是函数
高阶函数
闭包
// 闭包 func adder() func(v int) int { sum := 0 return func(v int) int { sum += v return sum } } Python 闭包
[Read More]
面向接口
Go学习笔记
Duck Typing(鸭子类型) 在维基百科中是这样定义的:
鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。
而鸭子类型这一名字出自James Whitcomb Riley在鸭子测试中提出的如下的表述:
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子
Duck Typing 的原话是,走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么它就是一只鸭子。
所谓duck typing,可以联想到它的推导,并不在乎类型的真正实体,只关心它的行为有duck的特性(描述事物的外部行为而非内部结构),那么就可以把它当做一只duck来看到
一般来讲,使用 duck typing 的编程语言往往被归类到“动态类型语言”或者“解释型语言”里,比如 Python, Javascript, Ruby 等;而其它的类型系统往往被归到“静态类型语言“中,比如 C/C++/Java。在不同的编程语言中,Duck Typing 实现方式:
Python 中的 Duck Typing def download(retriever): return retriever.get("www.baidu.com"); 有一个 download 函数,传过来一个 retriever 参数,retriever 是可以获取一个 url 链接的资源的。 这个 retriever 就是一个 Duck Typing 的对象,使用者约定好这个 retriever 会有一个 get 函数就可以了。 显然这个 download 函数会有以下问题:
运行时才知道传入的 retriever 有没有 get 函数。那么站在 download 函数的使用者的角度上看,怎么知道需要给 retriever 实现 get 方法呢?不可能去阅读 download 函数的代码,实际情况中,可能 download 函数的代码很长,可能 retriever 不只要实现 get 方法,还有其它方法需要实现。通常这种情况需要通过加注释来说明。
[Read More]
面向对象
Go学习笔记
Go语言仅支持封装,不支持继承和多态
Go语言没有class,只有struct
结构体和方法 创建
type Node struct { Value int Left, Right *Node } 定义方法
func CreateNode(value int) *Node { return &Node{Value: value} } func (node *Node) SetValue(value int) { if node == nil { fmt.Println("Setting Value to nil " + "node. Ignored.") return } node.Value = value } 只有使用指针才可以改变结构内容
nil指针也可以调用方法
值接收者 vs 指针接收者
要改变内容必须使用指针接收者 结构过大也考虑使用指针接收者 一致性:如果指针接收者,最好都是指针接收者 包和封装 结构定义的方法必须放在同一个包内 可以是不同的文件 如果包中内容需要public,需要将包中的变量,方法名等首字母大写 类型扩展 定义别名 使用组合 GOPATH及目录结构 GOPATH环境变量
[Read More]
容器
Go学习笔记
数组
切片(Slice)
1、扩展
arry := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println("arry = ", arry)
s1 = arry[2:6]
s2 = s1[3:5]
Slice可以向后扩展,不可以向前扩展
s[i]不可以超越len(s),向后扩展不可以超越底层数组cap(s)
添加元素时,如果超越cap,系统会重新分配更大的底层数组
由于值传递的关系,必须接收append的返回值。如:s = append(s, val)
Slice实现原理
Map
Map创建:make(map[string]int)
获取元素:m[key]
key不存在时,获取Value类型的初始值
使用value, ok := m[key]来判断是否存在key
使用delete函数删除一个key
使用range遍历key,或者key/value对
不保证遍历顺序,如需要顺序,需手动对key进行排序
使用len函数获取元素个数
map的key适用类型
- map使用哈希表,必须可以相等比较
- 除了slice, map, function的内建类型都可以作为key
- Struct类型不包含上述字段,也可以作为key
程序结构
Go学习笔记
条件语句 1、if…else… 条件内可以赋值
条件内赋值的变量作用域就存在于这个if语句里
if contents, err := ioutil.ReadFile(filename); err == nil { fmt.Println(string(contents)) } else { fmt.Println("cannot print file contents: ", err) } 2、switch switch后可以没有表达式
不需要显示break,switch会自动break,除非使用fallthrough
func grade(score int) string { g := "" switch { case score < 0 || score > 100: panic(fmt.Sprintf("Wrong score: %d", score)) case score < 60: g = "F" case score < 80: g = "C" case score < 90: g = "B" case score <= 100: g = "A" } return g } 3、for 表达式不需要括号
[Read More]