前言

本周是正式系统性学习整理Go语言的第二周,上周把基础知识学过后,这周开始学习进阶知识。说到Go语言的特性,那就是高并发,所以进阶学习的第一部分只是就是关于并发的。总结内容和之前一样,并不会过多的记录书本里的东西,更多的是这一周学习的困难和解决的过程。

学习思考

本周学习内容主要有两个部分,并发基础和算法,学习这些的原因是因为最近开始找实习了,发现确实落下了太多东西。

什么是并发

第一次了解并发是在学习计算机系统的时候,不再只让计算机做完一件事之后再开始做另外一件事,而是让计算机同时做多件事情,这就是并发。

说到并发就一定绕不开两个概念——进程和线程。

  • 在启动一个软件的时候,操作系统会为这个软件创建一个进程,这个进程就是该软件的工作空间,它包含了软件运行所需的所有资源。
  • 线程是进程的执行空间,一个进程可以有多个线程,线程被操作系统调度执行。

一个程序启动就会有一个进程被创建,同时进程也会启动一个线程,这个线程被称为主线程,如果主线程结束,那么整个程序就退出了。有了主线程,就可以从主线程里启动很多其他线程,也就有了多线程的并发


在Go语言中并没有线程的概念,只有协程,也成为 goroutine 先比线程来说,协程更加轻量,一个程序可以随意启功成千上万个协程。

goroutineGo routime所调度,也就是说,Go语言的并发是由Go自己调度的,这对于开发者来说完全透明。

如何通信

当我们同时启动了多个 goroutine 时,它们之间要怎么通信?

Go语言提供了用于通信的管道—— channel

  • 当需要从管道中接受值时,如果管道为空,那么会阻塞等待,知道管道中有值可以接受为之。

管道分类:

  • 无缓冲管道:容量为0,只起到传输数据的作用,又被称为同步管道
  • 有缓冲管道:类似于一个可阻塞的对列,内部元素先进先出

可以通过 select + channel 来实现多路复用的效果。

在Go语言中,提倡通过通信来共享内存,而不是通过共享内存来通信,所以在数据流动、传递的场景中要优先使用 channel,它是并发安全的。


思考题:channel是如何实现并发安全的?

channel内部使用了互斥锁来保证并发的安全。

并发中的Map是否安全

Go 中的标准 map 类型是非并发安全的,这意味着在多个 Goroutine 中并发读写同一个 map 可能导致数据竞争和不确定的行为。为了在并发环境中使用 map,Go 提供了 sync 包中的 sync.Map 类型,它是一种并发安全的映射。

无须初始化,直接声明即可。
sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。

sync.Map提供的常用方法有如下七个:

  • Load:通过参数key查询对应的value,如果不存在则返回nil;ok表示是否找到对应的值。
  • Store:该方法相当于对sync.Map的更新或新增,参数是键值对。
  • LoadOrStore:该方法的参数为key和value。该方法会先根据参数key查找对应的value,如果找到则不修改原来的值并通过actual返回,并且loaded为true;如果通过key无法查找到对应的value,则存储key-value并且将存储的value通过actual返回,loaded为false。
  • Delete:通过key删除键值对。
  • LoadAndDelete:通过key删除键的值,如果有,则返回上一个值。
  • Range:遍历sync.Map的元素,注意for…range map是对内置map类型的用法,sync.Map需要使用单独的Range方法。

并发安全的 sync.Map 演示代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
"fmt"
"sync"
)

//声明sync.Map
var syncmap sync.Map

func main() {

//Store方法将键值对保存到sync.Map
syncmap.Store("zhangsan", 97)
syncmap.Store("lisi", 100)
syncmap.Store("wangmazi", 200)

// LoadOrStore key不存在
v, ok := syncmap.LoadOrStore("three",3)
fmt.Println(v, ok) // 3 false
// LoadOrStore key存在
v, ok = syncmap.LoadOrStore("wangmazi", 200)
fmt.Println(v, ok) // 200 ture

// Load方法获取sync.Map 键所对应的值
fmt.Println(syncmap.Load("lisi"))

// Delete方法键删除对应的键值对
syncmap.Delete("lisi")

var syncmap sync.Map
// LoadAndDelete key不存在
v, ok = syncmap.LoadAndDelete("xiaomi")
fmt.Println(v, ok) // <nil> false
syncmap.Store("xiaomi", "xiaomi")
// LoadAndDelete key存在
v, ok = syncmap.LoadAndDelete("xiaomi")
fmt.Println(v, ok) // xiaomi true

// Range遍历所有sync.Map中的键值对
syncmap.Range(func(k, v interface{}) bool {
fmt.Println(k, v)
return true
})

}
  • 声明 score,类型为 sync.Map,注意,sync.Map 不能使用 make 创建。
  • 将一系列键值对保存到 sync.Map 中,sync.Map 将键和值以 interface{} 类型进行保存。
  • Range() 方法可以遍历 sync.Map,遍历需要提供一个匿名函数,参数为 k、v,类型为 interface{},每次 Range() 在遍历一个元素时,都会调用这个匿名函数把结果返回。
  • sync.Map 没有提供获取 map 数量的方法,替代方法是在获取 sync.Map 时遍历自行计算数量,sync.Map 为了保证并发安全有一些性能损失,因此在非并发情况下,使用 map 相比使用 sync.Map 会有更好的性能。

刷题思考

已经连续五天的刷题生活或许并没有给我带来什么真正有意义的思考,但还是想要记录一下。

毕竟从大一下学完数据结构之后就基本没怎么做过算法题了,所以刚开始这几天还是在找做题的感觉,有一种在高三备战高考的感觉,有时候也会觉得很折磨,看着别人想出来的完美算法,就会觉得自己和别人的差距太大了,只能一步一步来了。

这一周都只做了关于数组方面的题目,更多看到的题目标签是贪心、动态规划等等熟悉又陌生的字眼。

总之在做了几天题之后,发现自己的思维确实变慢了不少,还有就是把自然语言转换成代码语言的能力不行。

菜就多练喽!

小结

本周学习内容主要是并发和算法,在Go语言项目上并没有投入什么精力,认真学习过并发之后就会正式开始学习做一些小项目来补充一下这方面的空白。

算法方面就希望下周能够找到做题的感觉,希望自己的小脑袋瓜赶紧转起来吧,继续积累经验。

关于Go语言八股文也确实应该腾出更多的时间来看了,希望下周能够完成计划吧。