所有的Go程序都由最基本的函数和变量构成,函数和变量被组织到一个个单独的Go源文件中,这些源文件再按照作者的意图组织成合适的package,最终这些package有机地组成一个完整的Go语言程序。虽然Go语言对函数的名字没有太多的限制,但是main包中的main()函数默认是每一个可执行程序的入口。而package则用于包装和组织相关的函数、变量和常量。在使用一个package之前,我们需要使用import语句导入包。
GO的源文件采用UTF8编码。
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, World!")
}
1 GO的基本语法
Go语言的赋值和函数传参规则很简单,除闭包函数以引用的方式对外部变量访问之外,其他赋值和函数传参都是以传值的方式处理。
1.1 变量
Go 语言的变量名由字母、数字、下画线组成,首个字符不能为数字。Go 语法规定,定义的局部变量若没有被调用会发生编译错误。
var age int
//批量声明变量
var (
a int
b string
c []float32
d func() bool
e struct{
x int
y string
}
)
//简短声明
name := "Rubin"
未被初始化的变量会被初始化为默认零值:数字型为0,string为空字符串"",布尔型为false,函数、指针、切片为nil。
注意:省略var关键字的声明方式只能被用在函数体内,而不可以用于全局变量的声明与赋值。且该变量名必须是没有定义过的变量,否则,将发生编译错误。
多个短变量声明和赋值中,至少要有一个新声明的变量出现在左侧,那么即便其他变量名是重复声明的,编译器也不会报错,如下所示。在使用过程中应尽量避免这种重复声明。
var a = 10
a, b := 20, 30
Go语言中有匿名变量,用于站位。匿名变量声明为下划线 "_",匿名变量既不占用命名空间,也不会分配内存。
1.2 数据类型
Go语言中,有以下几种数据类型。
基本数据类型(原生数据类型):整型、浮点型、复数型、布尔型、字符串、字符(byte、rune)。
复合数据类型(派生数据类型):数组(array)、切片(slice)、映射(map)、函数(function)、结构体(struct)、通道(channel)、接口(interface)、指针(pointer)。
整型:
有符号整型:int8、int16、int32、int64、int
无符号整型:uint8、uint16、uint32、uint64、uint
浮点型:float32 、float64
复数型: complex64、complex128 由float32/float64的实部和虚部表示
布尔型:true、false 布尔型无法参与数值运算,也无法与其他类型进行转换。
字符串:
双引号书写字符串被称为字符串字面量(string literal),这种字面量不能跨行。
多行字符串需要使用反引号“`”,多用于内嵌源码和内嵌数据。在反引号中的所有代码不会被编译器识别,而只是作为字符串的一部分。
字符:byte、run 定义时使用单引号。
//byte 占 1 字节,表示UTF-8字符串中的单个字节的值,uint8的别名类型
//run 占 4 字节,表示单个unicode的字符,int32的别名类型
var a byte = 'a'
var b run = '中'
1.3 打印格式
通用的打印格式:
格式 | 输出 |
%v | 值的默认格式表示 |
%+v | 类似于%v,但打印结构体时会加上字段名 |
%#v | 值的GO语法表示 |
%T | 值的类型的GO语法表示 |
布尔类型的打印格式:
%t 打印单词true 或 false
整型的打印格式:
格式 | 输出 |
%b | 二进制 |
%c | 对应的unicode的码值 |
%d | 十进制 |
%8d | 该整型长度是8,不足的补空格表示 |
%08d | 该整型长度是8,不足的补0表示 |
%o | 八进制 |
%x | 十六进制 a~f 表示 |
%X | 十六进制 A~F 表示 |
%U | unicode格式,等价于“U+%04X” |
浮点型的打印格式:
格式 | 输出 |
%b | 无小数部分,二进制科学记数法表示 |
%e | (%.6e)有6位小数部分的科学记数法表示 |
%E | 科学记数法表示 |
%f | (%.6f)有6位小数部分 |
%F | 等价于%f |
%g | 根据情况采用%e或%f |
%G | 根据情况采用%E或%F |
字符串的打印格式:
格式 | 输出 |
%s | 直接输出字符串或者字节数组 |
%x | 每个字符用两个十六进制数字表示,a~f |
%X | 每个字符用两个十六进制数字表示,A~F |
1.4 类型转换
Go语言采用数据类型前置加括号的方式进行类型转换,格式如:T(表达式)。T表示要转换的类型;表达式包括变量、数值、函数返回值等。
var a int = 100
b := string(a)
1.5 常量
常量中的数据类型只可以是布尔型、数字型(整型、浮点型和复数型)和字符串。常量声明时加const即可。常量定义后未被使用,不会在编译时报错。
常量组中如果不指定类型和初始值,则与上一行非空常量的值相同。
iota,特殊常量值,是一个系统定义的可以被编译器修改的常量值。iota只能被用在常量的赋值中,在每一个const关键字出现时,被重置为0,然后每出现一个常量,iota所代表的数值会自动增加1。
1.6 类型别名
通过 type 关键字定义类型别名。别名类型只会在代码中存在,编译完成时不会有别名类型。
//定义类型别名
type MyString = string
//定义新的类型
type NewString string
2 Go的内置数据结构
2.1 数组
Go语言中,未初始化的数组不是nil,也就是说没有空数组(与切片不同)。
数组的长度是数组的一个内置常量,通过将数组作为参数传递给 len()函数,可以获得数组的长度。
var arrA = [3]int{2, 3, 4}
arrB := [...]int{21, 22, 23}
num := len(arrB)
Go语言中的数组并非引用类型,而是值类型。
将数组作为函数参数进行传递,它们将通过值传递,原始数组依然保持不变。
2.2 字符串
2.3 切片(slice)
切片的语法和数组很像,但数组是值类型,而切片是引用类型。切片只是底层数组的一个引用,对切片所做的任何修改都将反映在底层数组中。切片的数据结构可理解为一个结构体,这个结构体包含了三个元素:
指针--指向数组中切片指定的开始位置;
长度--切片的长度;
容量--切片开始位置到数组的最后位置的长度。
切片不需要说明长度。未初始化的切片为空切片,默认为nil,长度为0。使用make创建切片的语法如下:
var slice = make([]type, len, capacity) //capacity为可选参数
var sliceA []string
sliceB := []int{2, 3, 4, 5, 6}
sliceC := make([]int, 3)
sliceC.append(sliceC, 100)
sliceC.append(sliceC, 200)
sliceC.append(sliceC, 300)
arr :=[3]int{20, 50, 70}
sliceD := arr[:]
切片可以通过len()方法获取长度,通过cap()方法获取容量, 通过append()函数往切片中追加新元素。可以向切片里面追加一个或者多个元素,也可以追加一个切片。
append()会改变切片所引用的数组的内容,从而影响到引用同一数组的其他切片。当使用append()追加元素到切片时,如果容量不够,Go就会创建一个新的内存地址来储存元素。
可利用切片截取及append()函数实现切片元素的删除:
//切片截取删除第一个元素
sliceA = sliceA[1:]
//append()删除中间元素
a :=int(len(sliceA)/2)
sliceA = append(sliceA[:a], sliceA[a+1:]...)
2.4 map
Go语言的map是由Hash表实现的,所以对map的读取顺序不固定。
map和切片一样,也是一种引用类型。
len()函数同样可以返回map中键值对的数量,但是cap()函数不适用于map。
同一个map中的key必须保证唯一。key的数据类型必须是可参与比较运算的类型,也就是支持==或!=操作的类型,如布尔型、整型、浮点型、字符串、数组。
切片、函数等引用类型则不能作为key的数据类型。map的value可以是任何数据类型。
未初始化的map的默认值是nil。nil map不能存放键值对。如果要使用map存储键值对,必须在声明时初始化,或者使用make()函数分配到内存空间。
mapA :=map[string]string{
"China": "Beijing",
"France": "Paris",
"Japan": "Tokyo",
}
var mapB = make(map[string]string)
mapB["Italy"] = "Rome"
mapB["India"] = "New Delhi"
//检查元素是否存在
if v,ok := mapA["Japan"]; ok {
//删除元素
delete(mapA, "Japan")
}else{
fmt.Println("未检测到Japan的信息!")
}
for k, v:= range mapB {
fmt.Println("国家:", k, " 首都:", v)
}
delete(map, key)用于删除元素。Go语言没有为map提供清空所有元素的函数,清空map的唯一办法是重新make一个新的map。
3 流程控制
3.1 条件判断
if 语句
if...else语句
if嵌套语句
大括号{}必须存在,即使只有一行语句。
左括号必须在if或else的同一行。
在if之后,条件语句之前,可以添加变量初始化语句,使用“;”进行分隔。
3.2 条件分支
switch 语句
select 语句 //类似于switch,select会随机执行一个可运行的case,如果没有可运行的case它将阻塞,直到有可运行的case。
switch语句的case后不用接break,Go语言在case后默认自带break,匹配成功后不会执行后序case,而是跳出switch。可以添加fallthrough,强制执行后面的case分支。fallthrough 必须放在case分支的最后一行。如果它出现在中间的某个地方,编译器就会报错。
case后的值不能重复,但可以同时测试多个符合条件的值,也就是说case后可以有多个值,这些值之间使用逗号分隔,例如:case val1, val2, val3。
switch后的表达式可以省略,默认是switch true。
3.3 循环语句
for循环
嵌套循环
循环控制语句:
break、continue、goto
4 函数与指针
4.1 函数
4.1.1 函数声明
函数声明使用 func 关键字。Go函数的返回值可以有多个,可以是返回数据的数据类型,也可以是 变量名+变量类型 的组合。
Go语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。
在Go语言中,函数也是一种类型,可以和其他类型(如int32、float等)一样被保存在变量中。在Go语言中可以通过type来定义一个自定义类型。
Go语言支持匿名函数。
4.1.2 闭包
闭包是由函数和与其相关的引用环境组合而成的实体。
闭包只是在形式和表现上像函数,但实际上不是函数。
函数是编译器静态的概念,而闭包是运行期动态的概念。
对象是附有行为的数据,而闭包是附有数据的行为。
由于闭包函数“捕获”了和它在同一作用域的其他常量和变量,所以当闭包在任何地方被调用,闭包都可以使用这些常量或者变量。它不关心这些变量是否已经超出作用域,只要闭包还在使用这些变量,这些变量就依然存在。
4.1.3 可变参数
可变参数的特殊语法是三个点 “...”,在一个变量后面加上三个点,表示从该处开始接受可变参数。
当要传递若干个值到可变参数函数中时,可以手动书写每个参数,也可以将一个slice传递给该函数,通过“...”可以将slice中的参数对应地传递给函数。
一个函数最多只能有一个可变参数。
若参数列表中还有其他类型参数,则可变参数写在所有参数的最后。
4.2 指针
Go语言指针的最大特点是:指针不能运算(不同于C语言)。
Go语言的空指针的值是nil。
4.3 函数的参数传递
4.3.1 值传递(传值)
默认情况下,Go语言使用的是值传递,即在调用过程中不会影响到原内容数据。
4.3.2 引用传递(传引用)
引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到原内容数据。
严格来说Go语言只有值传递这一种传参方式,Go语言是没有引用传递的。
Go语言中可以借助传指针来实现引用传递的效果。函数参数使用指针参数,传参的时候其实是复制一份指针参数,也就是复制了一份变量地址。
Go语言中slice、map、chan类型的实现机制都类似指针,所以可以直接传递,而不必取地址后传递指针。
传引用和引用类型是两个概念。虽然Go语言只有传值一种方式,但是可以通过传引用类型变量达到与传引用一样的效果。
5 Go语言的面向对象编程
Go语言并没有提供类(class),但是它提供了结构体(struct),方法(method)可以在结构体上添加。与类相似,结构体提供了捆绑数据和方法的行为。
Go语言也没有沿袭传统面向对象编程中的诸多概念,比如继承、虚方法、构造方法和析构方法等。虽然Go语言没有继承和多态,但是可以通过匿名字段实现继承,通过接口实现多态。
5.1 结构体
结构体是值类型。
5.1.1 匿名结构体
匿名结构体就是没有名字的结构体,无须通过type关键字定义就可以直接使用。创建匿名结构体时,同时要创建对象。匿名结构体由结构体定义和键值对初始化两部分组成。
cat := struct{
name, color string
age int
}{
name: "Tom",
color: "gray",
age: 8,
}
5.1.2 结构体中的匿名字段
如果字段没有名字,那么默认使用类型作为字段名,同一个类型只能有一个匿名字段。
5.1.3 结构体嵌套
结构体嵌套可以模拟面向对象编程中的 聚合关系 和 继承关系。
结构体嵌套中采用匿名结构体字段可以模拟继承关系,而聚合关系必须使用有名字的结构体作为字段。
在结构体中,属于匿名结构体的字段称为提升字段,它们可以被访问,匿名结构体就像是该结构体的父类。
5.2 方法
Go语言同时有函数和方法,方法的本质是函数,但是方法和函数又有所不同。
5.2.1 方法和函数
函数(function)是一段具有独立功能的代码,可以被反复多次调用,从而实现代码复用;而方法(method)是一个类的行为功能,只有该类的对象才能调用。
方法有接受者,而函数无接受者。接受者的概念类似于传统面向对象语言中的this或self关键字。Go语言中,接受者可以是结构体,也可以是结构体类型外的其他任何类型。
函数不可以重名,而方法可以重名。只要接受者不同,方法名就可以相同。
一个方法就是一个包含了接受者的函数。
一段程序到底是要实现为函数还是方法,主要由以下两个原因决定:
Go不是一种纯粹面向对象的编程语言,它不支持类。因此方法旨在实现类似于类的行为。
相同名称的方法可以在不同的类型上定义,而具有相同名称的函数是不允许的。
在定义方法时,接受者变量的命名建议使用接受者类型的第一个小写字母。若方法的接受者不是指针,实际只是获取了一个拷贝,而不能真正改变接受者中原来的数据。
5.2.2 方法继承
方法是可以继承的,如果匿名字段实现了一个方法,那么包含这个匿名字段的struct也能调用该匿名字段中的方法。
5.2.3 方法重写
方法重写是指一个包含了匿名字段的struct也实现了该匿名字段实现的方法。
5.3 接口
在Go语言中,接口是一组方法签名。接口指定了类型应该具有的方法,类型决定了如何实现这些方法。当某个类型为接口中的所有方法提供了具体的实现细节时,这个类型就被称为实现了该接口。
Go语言的类型都是隐式实现接口的。任何定义了接口中所有方法的类型都被称为隐式地实现了该接口。
Go没有implements或extends关键字,这类编程语言叫作duck typing编程语言。使用duck typing的编程语言往往被归类为“动态类型语言”或者“解释型语言”。
5.3.1 多态
Go语言中的多态性是在接口的帮助下实现的——定义接口类型,创建实现该接口的结构体对象。
5.3.2 空接口
空接口中没有任何方法,任意类型都可以实现该接口。
空接口常用于以下情形:
println的参数就是空接口。
定义一个map,key是string,value是任意数据类型。
定义一个切片,其中存储任意类型的数据。
5.3.4 接口对象转型
//方式一:通过 ok值判断
if instance, ok := interfaceObj.(实际类型); ok {
...
}
//方式二:通过 switch...case 语句判断
switch interfaceObj.(type) {
case Person:
...
case Cat:
...
}
6 异常处理
6.1 error
Go语言通过内置的错误类型提供了非常简单的错误处理机制,即error接口。接口定义如下:
type error interface {
Error() string
}
在Go语言中处理错误的方式通常是将返回的错误与nil进行比较。nil值表示没有发生错误,而非nil值表示出现错误。如果不是nil,需打印输出错误。
Go语言errors包对外提供了可供用户自定义的方法,errors包下的New()函数返回error对象,errors.New()函数创建新的错误。
6.2 defer
关键字defer用于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行。
defer语句只能出现在函数或方法的内部。
在函数中可以添加多个defer语句。如果有很多调用defer,当函数执行到最后时,这些defer语句会按照逆序执行(报错的时候也会执行),最后该函数返回。
defer语句经常被用于处理成对的操作,如打开-关闭、连接-断开连接、加锁-释放锁。特别是在执行打开资源的操作时,遇到错误需要提前返回,在返回前需要关闭相应的资源,不然很容易造成资源泄露等问题。
延迟函数的参数在执行延迟语句时被执行,而不是在执行实际的函数调用时执行。如下:
func main(){
a := 5
b := 7
defer printAdd(a, b) //a=5, b=7而不是7和9
a += 2
b += 2
printAdd(a, b)
}
6.2 panic 和 recover机制
panic()是一个内建函数,可以中断原有的控制流程。panic异常一旦被引发就会导致程序崩溃。不应通过调用panic()函数来报告普通的错误,而应该只把它作为报告致命错误的一种方式。
Go语言提供了专用于“拦截”运行时panic的内建函数recover()。recover()可以让panic的 goroutine 恢复过来,并重新获得流程控制权。但recover()必须在延迟函数中执行。
7 Go的并发编程
7.1 Goroutine
7.1.1 Coroutine与Goroutine
协程(Coroutine)是编译器级的,进程和线程是操作系统级的。协程不被操作系统内核管理,而完全由程序控制,因此没有线程切换的开销。和多线程比,线程数量越多,协程的性能优势就越明显。协程的最大优势在于其轻量级,可以轻松创建上万个而不会导致系统资源衰竭。
Go语言中的协程叫作Goroutine。Goroutine由Go程序运行时(runtime)调度和管理,Go程序会智能地将Goroutine中的任务合理地分配给每个CPU。创建Goroutine的成本很小,每个Goroutine的堆栈只有几kb,且堆栈可以根据应用程序的需要增长和收缩。
Coroutine与Goroutine的不同:
Goroutine能并行执行,Coroutine只能顺序执行;
Goroutine可在多线程环境产生,Coroutine只能发生在单线程;
Coroutine属于协作式任务处理。应用程序在不使用CPU时,需要主动交出CPU使用权,系统才能获得控制权并将控制权交给其他Coroutine。如果开发者无意间让应用程序长时间占用CPU,操作系统也无能为力,计算机很容易失去响应或者死机。
Goroutine属于抢占式任务处理。应用程序对CPU的控制最终由操作系统来管理,如果操作系统发现一个应用程序长时间占用CPU,那么用户有权终止这个任务。
7.1.2 Goroutine的创建
在函数或方法前面加上关键字go,将会同时运行一个新的Goroutine。
go关键字后也可以是匿名函数或闭包。
使用go关键字创建Goroutine时,被调用的函数往往没有返回值,如果有返回值也会被忽略。如果需要在Goroutine中返回数据,必须使用channel,通过channel把数据从Goroutine中作为返回值传出。
7.1.3 调整并发的运行性能
在Go程序运行时,runtime实现了一个小型的任务调度器。此调度器的工作原理类似于操作系统调度线程,Go程序调度器可以高效地将CPU资源分配给每一个任务。在多个Goroutine的情况下,可以使用runtime.Gosched()交出控制权。
传统逻辑中,开发者需要维护线程池中的线程与CPU核心数量的对应关系,这在Go语言中可以通过runtime.GOMAXPROCS()函数做到。
7.2 Channel
channel即Go的通道,是协程之间的通信机制。一个channel是一条通信管道,它可以让一个协程通过它给另一个协程发送数据。每个channel都需要指定数据类型,即channel可发送数据的类型。如果使用channel发送int类型数据,可以写成chan int。数据发送的方式如同水在管道中的流动。
Go语言主张通过数据传递来实现共享内存,而不是通过共享内存来实现数据传递。
7.2.1 channel的创建及使用
chan是引用类型,空值是nil,声明后需要配合make()才能使用。
通过channel发送或接收数据时,需要使用特殊的操作符“<-”。
channel发送的值的类型必须与channel的元素类型一致。如果接收方一直没有接收,那么发送操作将持续阻塞。此时所有的Goroutine,包括main()的Goroutine都处于等待状态。
ch1 := make(chan int)
ch2 := make(chan interface{})
ch1 <- 120
//1.阻塞接收数据:执行该语句时channel将会阻塞,直到接收到数据并赋值给a变量。
a := <-ch1
//2.完整写法:ok表示是否接收到数据,通过ok值可以判断当前channel是否被关闭。
data, ok := <- ch1
//3.忽略接收数据:执行该语句时channel将会阻塞,其目的不在于接收channel中数据,而是为了阻塞Goroutine。
<-ch2
/* 4.循环接收数据
循环接收数据,需要配合使用关闭channel,借助普通for循环和for ... range语句循环接收多个元素。
普通for循环接收channel数据,需要有break循环的条件;for … range会自动判断出channel已关闭,而无须通过判断来终止循环。
*/
for {
b := <-ch2
if b == "" {
break;
}
...
}
for {
c, ok := <-ch1
if !ok {
break;
}
...
}
for v := range ch2 {
...
}
使用channel时要考虑发生死锁(deadlock)的可能。如果Goroutine在一个channel上发送数据,其他的Goroutine应该接收得到数据;如果没有接收,那么程序将在运行时出现死锁。如果Goroutine正在等待从channel接收数据,其他一些Goroutine将会在该channel上写入数据;如果没有写入,程序将会死锁。
7.2.2 阻塞
channel默认是阻塞的。当数据被发送到channel时会发生阻塞,直到有其他Goroutine从该channel中读取数据。当从channel读取数据时,读取也会被阻塞,直到其他Goroutine将数据写入该channel。这些channel的特性帮助Goroutine有效地通信,而不需要使用其他语言中的显式锁或条件变量。
7.2.3 关闭channel
发送方如果数据写入完毕,需要关闭channel,用于通知接收方数据传递完毕。通常情况是发送方主动关闭channel。
往关闭的channel中写入数据会报错:panic: send on closed channel。但是可以从关闭后的channel中读取数据,返回数据的默认值和false。
ch1 := make(chan string)
ch1 <- 270
close(ch1)
7.2.4 缓冲channel
默认创建的都是非缓冲channel,读写都是即时阻塞。缓冲channel自带一块缓冲区,可以暂时存储数据,如果缓冲区满了,就会发生阻塞。
ch1 := make(chan string, 6)
7.2.5 单向channel
channel默认都是双向的,即可读可写。定向channel也叫单向channel,只读,或只写。
直接创建单向channel没有任何意义。通常的做法是创建双向channel,然后以单向channel的方式进行函数传递。
//只读
chr := make(<-chan int)
<-chr
//只写
chw := make(chan<- string)
chw <- "china"
func func1(chr <-chan float)
func func2(chr chan<- float)
7.3 select语句
select语句的机制有点像switch语句,不同的是,select会随机挑选一个可通信的case来执行,如果所有case都没有数据到达,则执行default,如果没有default语句,select就会阻塞,直到有case接收到数据。
7.4 sync包
sync包提供了互斥锁。除了Once和WaitGroup类型,其余多数适用于低水平的程序,多数情况下,高水平的同步使用channel通信性能会更优一些。sync包类型的值不应被复制。
7.4.1 同步等待组
同步的sync是串行执行,异步的sync是同时执行。
WaitGroup同步等待组,定义如下:
type WaitGroup struct {
noCopy noCopy
statel [12]byte
sema uint32
}
WaitGroup,即等待一组Goroutine结束。父Goroutine调用Add()方法来设置应等待Goroutine的数量。每个被等待的Goroutine在结束时应该调用Done()方法。与此同时,主Goroutine可调用Wait()方法阻塞至所有Goroutine结束。
Add()方法向内部计数加上delta,delta可以是负数;如果内部计数变为0,Wait()方法阻塞等待的所有Goroutine都会释放,如果计数小于0,则该方法panic。注意Add()加上正数的调用应在Wait()之前,否则Wait()可能只会等待很少的Goroutine。通常来说,本方法应该在创建新的Goroutine或者其他应该等待的事件之前调用。
Done()方法减小WaitGroup计数器的值,应在Goroutine的最后执行。
Wait()方法阻塞Goroutine直到WaitGroup计数减为0。
7.4.2 互斥锁
互斥锁定义如下:
type Mutex struct {
state int32
sema uint32
}
func (m *Mutex) Lock()
func (m *Mutex) Unlock()
Mutex是一个互斥锁,可以创建为其他结构体的字段;零值为解锁状态。Mutex类型的锁和 Goroutine无关,可以由不同的Goroutine加锁和解锁。
7.4.3 读写互斥锁
读写互斥锁的定义如下:
type RWMutex struct {
w Mutex
writerSem uint32
readerSem uint32
readerCount int32
readerWait int32
}
// 锁定为写状态
func (rw *RWMutex) Lock()
//解锁写状态
func (rw *RWMutex) UnLock()
//锁定读状态
func (rw *RWMutex) RLock()
//解锁读状态
func (rw *RWMutex) RUnlock()
RWMutex是读写互斥锁,简称读写锁。该锁可以同时被多个读取者持有或被唯一个写入者持有。RWMutex可以创建为其他结构体的字段;零值为解锁状态。RWMutex类型的锁也和Goroutine无关,可以由不同的Goroutine加读取锁/写入锁和解读取锁/写入锁。
读写锁的使用中,写操作都是互斥的,读和写是互斥的,读和读不互斥。
7.4.4 条件变量
条件变量定义如下:
type Cond struct {
noCype noCopy
L Locker
notify notifyList
checker copyChecker
}
//使用锁l创建一个*Cond。Cond条件变量总是要和锁结合使用
func NewCond(l Lock) *Cond
//唤醒所有等待c的Goroutine。在调用本方法时,建议(但并非必须)保持c.L的锁定
func (c *Cond)Broadcast()
//唤醒等待c的一个Goroutine(如果存在)。在调用本方法时,建议(但并非必须)保持c.L的锁定
func (c *Cond)Signal()
//自行解锁c.L并阻塞当前Goroutine,待线程恢复执行时,Wait()方法会在返回前锁定c.L。
//和其他系统不同,Wait()除非被Broadcast()或者Signal()唤醒,否则不会主动返回。
//此方法广播给所有人
func (c *Cond)Wait()
Cond实现了一个条件变量,一个Goroutine集合地,供Goroutine等待或者宣布某事件的发生。
每个Cond实例都有一个相关的锁(一般是*Mutex或*RWMutex类型的值),它须在改变条件时或者调用Wait()方法时保持锁定。Cond可以创建为其他结构体的字段,Cond在开始使用后不能被复制。条件变量sync.Cond是多个Goroutine等待或接受通知的集合地。
8 文件I/O操作
8.1 FileInfo接口
type FileInfo Interface {
Name() string
Size() int64
Mode() FileMode
ModTime() time.Time
IsDir() bool
Sys() interface{}
}
type fileStat struct{
name string
size int64
mode FileMode
modTime time.Time
sys syscall.Stat_t
}