目录
- switch
- type
- 指针
- 结构体
- 结构体初始化
- 数组
- 数组切片
- Map
- select
- 并发
- channel
- 定义channel
- 单向channel
- 关闭channel
- channel 缓冲机制
- channel 超时机制
- channel的传递
- Mutex
- RWMutex
switch
switch type {
case 1:
return "a"
case 2:
return "a";
case 3:
return "b"
default:
return "c"
}
type
- 定义结构体
- 定义类型,相当于定义一个别名
- 定义接口类型
- 定义函数类型
指针
究其原因,是因为Go语言和C语言一样,类型都是基于值传递的。要想修改变量的值,只能传递指针。
只有在你需要修改对象的时候,才必须用指针(这里使用 *a += b
是因为只有*a
的类型才是Integer
,才能和b
进行运算, 如果只是a 的话,a的类型是 *Integer
)
结果是: a=3
如果你实现成员方法时传入的不是指针而是值(即传入Integer
,而非*Integer
),如下所示
结果是a=1
,也就是维持原来的值
以下为示例代码:
type Person struct {
Name string
Age int
}
type Bird struct {
Color string
}
//为Person类创建walk方法
func (p *Person) walk() {
p.Name = "Anna"
fmt.Println(p.Name + " is walking")
}
//为bird类创建fly方法
func (b Bird) fly() {
b.Color = "yellow"
fmt.Println("a " + b.Color + " bird is flying")
}
func main() {
p := &Person{"Jason", 28}
p.walk()
fmt.Println(p.Name + " is walking")
b := &Bird{"red"}
b.fly()
fmt.Println("a " + b.Color + " bird is flying")
}
===============================
Anna is walking
Anna is walking
a yellow bird is flying
a red bird is flying
===============================
结构体
结构体(struct
)和其他语言的类(class
)有同等的地位
结构体初始化
未进行显式初始化的变量都会被初始化为该类型的零值,
例如bool
类型的零
值为false
, int
类型的零值为0
, string
类型的零值为空字符串
数组
在Go语言中数组是一个值类型
(value type)。
所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作。
如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。
因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所传入数组的一个副本。
数组切片
Go语言支持用myArray[first:last]
这样的方式来基于数组生成一个数组切片
不包含first
,包含last
。index从1
开始
a := "123456789"
DD(a[1:], a[2:3])
===============================
-val: 23456789
===============================
-val: 3
DD(a[1:], a[9:9], len(a))
===============================
type: string
#val: "23456789"
-val: 23456789
===============================
type: string
#val: ""
-val:
===============================
type: int
#val: 9
-val: 9
===============================
基于数组
直接创建
操作函数
cap()
函数返回的是数组切片分配的空间大小。
len()
函数返回的是数组切片中当前所存储的元素个数。
append()
第二个参数其实是一个不定参数
,我们可以按自己需求添加若干个元素,
甚至直接将一个数组切片追加到另一个数组切片的末尾。
如果没有这个省略号的话,会有编译错误,因为按append()
的语义,从第二个参数起的所有参数都是待附加的元素。因为mySlice
中的元素类型为int
,所以直接传递mySlice2
是行不通的。加上省略号相
当于把mySlice2包含的所有元素打散后传入。
Map
Map 变量声明
Map 变量创建
Map 元素赋值
Map 元素删除
Map 元素查找
Map 元素赋值的注意点
以上图中给result2赋值时出现的波浪线部分,提示
因为 var result2 map[string]int
只是申明了变量,但他没有初始化
采用 make(map[string]int)
的方式就可以避免这种风险
定义channel
channel是Go语言在语言级别提供的goroutine间的通信方式。 我们可以使用channel在两个或多个goroutine之间传递消息。
channel是类型相关的。也就是说,一个channel只能传递一种类型的值,这个类型需要在声 明channel时指定。如果对Unix管道有所了解的话,就不难理解channel,可以将其认为是一种类 型安全的管道。
定义一个没有缓冲区的channel只有写入没有读取的话,程序直接锁死
c := make(chan int)
c<-1
helper.Dump(len(c))
===============================
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
===============================
正确代码如下:
c := make(chan int)
go func(c chan int) {
<-c
}(c)
c<-1
helper.Dump(len(c))
===============================
type: int
#val: 0
-val: <nil>
===============================
如果定义了缓冲区则并不会报错,正常编译。代码如下:
c := make(chan int, 2)
c<-1
===============================
type: int
#val: 1
-val: <nil>
===============================
当写入超过缓冲区时,编译继续出错。代码如下:
c := make(chan int, 2)
c<-1
c<-1
c<-1
helper.Dump(len(c))
===============================
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
===============================
单向channel
channel本身必然是同时支持读写的,否则根本没法用。 假如一个channel真的只能读,那么肯定只会是空的,因为你没机会往里面写数据。 同理,如果一个channel只允许写,即使写进去了,也没有丝毫意义,因为没有机会读取里面 的数据。 所谓的单向channel概念,其实只是对channel的一种使用限制。
channel是一个原生类型,因此不仅支持被传递,还支持类型转换。 只有在介绍了单向channel的概念后,读者才会明白类型转换对于channel的意义:
就是在单向channel和双向channel之间进行转换。示例如下:
为什么要做这样的限制呢?从设计的角度考虑,所有的代码应该都遵循“最小权限原则”, 从而避免没必要地使用泛滥问题,进而导致程序失控。
关闭channel
这个用法与map中的按键获取value的过程比较类似, 只需要看第二个bool返回值即可,如果返回值是false则表示ch已经被关闭。
select
select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作
比如上面的例子中, 第一个case试图从chan1读取一个数据并直接忽略读到的数据, 而第二个case则是试图向chan2中写入一个整型数1,如果这两者都没有成功, 则到达default语句
并发
计算机的CPU从单内核(core)向多内核发展,而我们的程序都是串行的,计算机硬件的 能力没有得到发挥。 我们的程序因为IO操作被阻塞,整个程序处于停滞状态,其他IO无关的任务无法执行
并发能更客观地表现问题模型; 并发可以充分利用CPU核心的优势,提高程序的执行效率; 并发能充分利用CPU与其他硬件设备固有的异步性。
在一个函数调用前加上go关键字,这次调用就会在一个新的goroutine中并发执行。当被调用 的函数返回时,这个goroutine也自动结束了。需要注意的是,如果这个函数有返回值,那么这个 返回值会被丢弃
Go程序从初始化main package并执行main()函数开始,当main()函数返回时,程序退出, 且程序并不等待其他goroutine(非主goroutine)结束。 对于上面的例子,主函数启动了10个goroutine,然后返回,这时程序就退出了,而被启动的 执行Add(i, i)的goroutine没有来得及执行,所以程序没有任何输出。
Go语言提供的是另一种通信模型,即以消息机制而非共享内存(C共享内存是通过锁来控制争抢)作为通信方式。
channel
“不要通过共享内存来通信,而应该通过通信来共享内存。”
消息机制认为每个并发单元是自包含的、独立的个体,并且都有自己的变量,但在不同并发 单元间这些变量不共享。每个并发单元的输入和输出只有一种,那就是消息。这有点类似于进程 的概念,每个进程不会被其他进程打扰,它只做好自己的工作就可以了。不同进程间靠消息来通 信,它们不会共享内存。
channel是进程内的通信方式,因此通过channel传递对象的过程和调 用函数时的参数传递行为比较一致,比如也可以传递指针等。如果需要跨进程通信,我们建议用 分布式系统的方法来解决,比如使用Socket或者HTTP等通信协议。
执行结果如下:
channel 缓冲机制
channel 超时机制
如下代码:
不出问题的话一切都正常运行。但如果出现了一个错误情况,即永远都没有人往ch里写数据,那 么上述这个读取动作也将永远无法从ch中读取到数据, 导致的结果就是整个goroutine永远阻塞并 没有挽回的机会。
channel的传递
-->管道的实现-->流式处理数据
多核并行化
Mutex
当一个goroutine获得了Mutex后,其他goroutine就只能乖乖等到这个goroutine释放该Mutex。
RWMutex
相对友好些,是经典的单写多读模型。 在读锁占用的情况下,会阻止写,但不阻止读,也就是多个goroutine可同时获取读锁(调用RLock()方法;) 而写锁(调用Lock()方法)会阻止任何其他goroutine(无论读和写)进来,整个锁相当于由该goroutine独占。 从RWMutex的实现看, RWMutex类型其实组合了Mutex:
全局唯一操作
这段代码没有引入Once, setup()将会被每一个goroutine先调用一次,这至少对于这个 例子是多余的。
在现实中,我们也经常会遇到这样的情况。 Go语言标准库为我们引入了Once类型以解决这个问题。 once的Do()方法可以保证在全局范围内只调用指定的函数一次(这里指setup()函数), 而且所有其他goroutine在调用到此语句时,将会先被阻塞,直至全局唯一的once.Do()调用结束后才继续。