GO语言学习笔记

        所有的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 表示
%Uunicode格式,等价于“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
}

8.2 ioutil包

8.3 bufio包

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值