Go语言的线程

[TOC] 线程与锁 现代CPU一般含有多个核,并且一个核可能支持多线程。换句话说,现代CPU可以同时执行多条指令流水线。 为了将CPU的能力发挥到极致,我们常常需要使我们的程序支持并发(concurrent)计算。并发计算是指若干计算可能在某些时间片段内同时运行的情形。 在并行计算中,多个计算在任何时间点都在同时运行。并行计算属于特殊的并发计算。 而并发编程会存在数据竞争(data race)的情况,在不同线程同时修改统一内存控制时。并发编程的一大任务就是要调度不同计算,控制它们对资源的访问时段,以使数据竞争的情况不会发生。 此任务常称为并发同步(或者数据同步)。 锁是解决数据竞争的一种方法,在go语言中提供了sync包。 sync.Mutex: 互斥锁 sync.RWMutex: 读写分离锁 sync.WaitGroup: 等待一组goroutine 返回 sync.Once: 保证某段代码只执行一次 sync.Cond: 让一组goroutine 在满足特定条件时被唤醒 sync.Mutex Lock()加锁,Unlock()解锁 var m sync.Mutex func f1() { m.Lock() defer m.Unlock() doSomething() } func f2() { m.Lock() doSomething() m.Unlock() } sync.RWMutex 简单来说:不限制并发读,只限制并发写和并发读写 详细来说: 一个RWMutex值常称为一个读写互斥锁,它的内部包含两个锁:一个写锁和一个读锁。 对于一个可寻址的RWMutex值rwm,数据写入者可以通过方法调用rwm.Lock()对rwm加写锁,或者通过rwm.RLock()方法调用对rwm加读锁。 方法调用rwm.Unlock()和rwm.RUnlock()用来解开rwm的写锁和读锁。 rwm的读锁维护着一个计数。当rwm.RLock()调用成功时,此计数增1;当rwm.Unlock()调用成功时,此计数减1; 一个零计数表示rwm的读锁处于未加锁状态;反之,一个非零计数(肯定大于零)表示rwm的读锁处于加锁状态。 对于一个可寻址的RWMutex值rwm,下列规则存在: rwm的写锁只有在它的写锁和读锁都处于未加锁状态时才能被成功加锁。 换句话说,rwm的写锁在任何时刻最多只能被一个数据写入者成功加锁,并且rwm的写锁和读锁不能同时处于加锁状态。 当rwm的写锁正处于加锁状态的时候,任何新的对之加写锁或者加读锁的操作试图都将导致当前协程进入阻塞状态,直到此写锁被解锁,这样的操作试图才有机会成功。 当rwm的读锁正处于加锁状态的时候,新的加写锁的操作试图将导致当前协程进入阻塞状态。 但是,一个新的加读锁的操作试图将成功,只要此操作试图发生在任何被阻塞的加写锁的操作试图之前(见下一条规则)。 换句话说,一个读写互斥锁的读锁可以同时被多个数据读取者同时加锁而持有。 当rwm的读锁维护的计数清零时,读锁将返回未加锁状态。 假设rwm的读锁正处于加锁状态的时候,为了防止后续数据写入者没有机会成功加写锁,后续发生在某个被阻塞的加写锁操作试图之后的所有加读锁的试图都将被阻塞。 假设rwm的写锁正处于加锁状态的时候,(至少对于标准编译器来说,)为了防止后续数据读取者没有机会成功加读锁,发生在此写锁下一次被解锁之前的所有加读锁的试图都将在此写锁下一次被解锁之后肯定取得成功,即使所有这些加读锁的试图发生在一些仍被阻塞的加写锁的试图之后。 后两条规则是为了确保数据读取者和写入者都有机会执行它们的操作。 请注意:一个锁并不会绑定到一个协程上,即一个锁并不记录哪个协程成功地加锁了它。 example package main import ( "fmt" "runtime" "sync" ) type Counter struct { m sync.RWMutex n uint64 } func (c *Counter) Value() uint64 { c.m.RLock() defer c.m.RUnlock() return c.n } func (c *Counter) Increase(delta uint64) { c.m.Lock() c.n += delta c.m.Unlock() } func main() { var c Counter for i := 0; i < 100; i++ { go func() { for k := 0; k < 100; k++ { c.Increase(1) } }() } for c.Value() < 10000 { runtime.Gosched() } fmt.Println(c.Value()) // 10000 } sync.WatiGroup 每个sync.WaitGroup值在内部维护着一个计数,此计数的初始默认值为零。 ...

April 6, 2019

Go基础使用

[TOC] go控制语句 if 基本形式 // 大括号的左边必须跟在语句的后面 if condition1 { // do something } else if condition2 { // do something } else { // catch-all or default } 简短语句 // 处理完分号“;”前面的语句之后,再做判断 if v:=x-100; v<0 { // do something } switch fallthrough: 划过执行下一个case switch var1 { case value1: // do sth case value2: fallthrough // do next case case value3: // so sth default: ... } for // eg 1 for i:=0; i<10; i++ { sum += i } // eg 2 for ; sum < 1000; { sum += sum } // always loop for { // do something if condition { break } } for range // string for index,char := range myString { // do something } // map for k,v := range myMap { // do } // array or slice for index,value := range myArray { // do } 数据结构 variable var varName type ...

April 4, 2019

Go工具链

[TOC] Go 为了从任意目录运行Go,安装目录下的bin子目录路径必须配置在PATH环境变量中。 1、Go环境变量 GOPATH: 此环境变量的默认值为当前用户的HOME目录下的名为go文件夹对应的目录路径。 GOPATH文件夹中的pkg子文件夹用来缓存被本地项目所依赖的Go模块(一个Go模块为若干Go库包的集合)的版本。src子文件夹用来存放源码。 GOBIN: GOBIN环境变量用来指定go install子命令产生的Go应用程序二进制可执行文件应该存储在何处。 它的默认值为GOPATH文件夹中的bin子目录所对应的目录路径。 2、Go子命令 go run 编译和运行main包中的go程序。比如有一个 example.go 的文件,只需要执行 go run example.go 即可。 如果程序的main包中有多个go源码文件,我们可以指定目录。例如: $ ls a.go b.go $ go run ./ go install 编译和安装main包中的go程序,并不会执行,而是将可执行文件放入GOBIN指定的目录中。 我们可以运行go install example.com/program@latest来安装一个第三方Go程序的最新版本(至GOBIIN目录)。 在1.16版本之前,可以是用go get -u example.com/program 来安装。 go build 编译main包中的go程序,并不会安装,也不会执行。 go vet go vet子命令可以用来检查可能的代码逻辑错误(即警告)。 go fmt go fmt子命令来用同一种代码风格格式化Go代码。 go test go test子命令来运行单元和基准测试用例。 go doc go doc子命令用来(在终端中)查看Go代码库包的文档。 go mod init go mod init 命令可以用来在当前目录中生成一个go.mod文件。此go.mod文件将被用来记录当前项目需要的依赖模块和版本信息。 go mod tidy go mod tidy命令用来通过扫描当前项目中的所有代码来添加未被记录的依赖至go.mod文件或从go.mod文件中删除不再被使用的依赖。 ...

April 2, 2019

Go语言简洁

[TOC] Go语言简介 Go是一门编译型和静态型的编程语言。 1、Go语言卖点 1、做为一门静态语言,Go却和很多动态脚本语言一样得灵活 2、节省内存、程序启动快和代码执行速度快 3、内置并发编程支持 4、良好的代码可读性,Go的语法很简洁并且和其它流行语言相似。 5、良好的跨平台支持 6、一个稳定的Go核心设计和开发团队以及一个活跃的社区 7、Go拥有一个比较齐全的标准库 和C家族语言相比有以下优点: 程序编译时间短 像动态语言一样灵活 内置并发支持 2、Go语言特性 内置并发编程支持: 使用协程(goroutine)做为基本的计算单元。轻松地创建协程。 使用通道(channel)来实现协程间的同步和通信。 内置了映射(map)和切片(slice)类型。 支持多态(polymorphism)。 使用接口(interface)来实现裝盒(value boxing)和反射(reflection)。 支持指针。 支持函数闭包(closure)。 支持方法。 支持延迟函数调用(defer)。 支持类型内嵌(type embedding)。 支持类型推断(type deduction or type inference)。 内存安全。 自动垃圾回收。 良好的代码跨平台性。 3、开源Go项目 图片来源于极客时间

April 1, 2019