前端工程师学习go语言第二弹

简介: 前端工程师学习go语言第二弹

640.png


一、并发编程


1.1 基础


1.1.1 进程、线程、协程


  • 进程
  • 进程是程序在操作系统中的一次执行过程,系统进行分配和调度的一个独立单位
  • 线程
  • 线程是进程的一个执行实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位
  • 协程
  • 独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的


1.1.2 并发和并行


  • 并发
  • 多线程程序在一个核的cpu上运行,就是并发
  • 并行
  • 多线程程序在多个核的cpu上运行,就是并行

1.1.3 注意


  • Go语言的并发模型是CSP,提倡通过通信共享内存而不是通过共享内存而实现通信


1.2 goroutine


goroutine是由Go的运行时(runtime)调度和管理的,Go程序会智能地将goroutine中的任务合理地分配给每个CPU

1.2.1 使用


  • 只需要在调用函数的时候再前面加上go关键字,就可以为一个函数创建一个goroutine
  • 示例
  • go xxx()// 启动另外一个goroutine去执行xxx函数
  • 启动一个goroutine
  • 启动多个goroutine
// 用sync.WaitGroup实现goroutine的同步
var wg sync.WaitGroup
func hello(i int) {
    defer wg.Done() // goroutine结束就登记-1
    fmt.Println("Hello Goroutine!", i)
}
func main() {
    for i := 0; i < 10; i++ {
        wg.Add(1) // 启动一个goroutine就登记+1
        go hello(i)
    }
    wg.Wait() // 等待所有登记的goroutine都结束
}

1.2.2 goroutine调度


GPM是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统。

1.2.2.1 GMP


  1. GMP模型

640.jpg



  1. 各单元含义


  • G
  • 就是goroutine,里面除了存放本goroutine信息外,还有与所有P的绑定等信息
  • P
  • 管理着一组goroutine队列,P里面会存储当前goroutine运行的上下文环境(函数指针、堆栈地址及地址边界),P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等),当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了回去其它P的队列里抢任务
  • M
  • M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟,M与内核线程一般是一一映射的关系,一个goroutine最终是要放到M上执行的。
  1. 调度器的设计策略
  • 核心
  • 复用线程:避免频繁的创建、销毁线程,而是对线程的复用
  • 机制
  • 当本线程因为G进行系统调用阻塞时,线程释放绑定的P,把P转移给其他空闲的线程执行
  • 当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程
  • work stealing机制
  • hand off机制


  1. 详细解释


  1. P和M的个数


  • P的数量
  • 由启动时环境变量 GOMAXPROCS 个 goroutine 在同时运行。
  • M的数量
  • go语言本身的限制:go程序启动时,会设置M的最大数量,默认10000,但是内核很难支持这么多的线程数,所以这个限制可以忽略。
  • runtime/debug中的SetMaxThreads函数,设置M的最大数量
  • 一个M阻塞了,会创建新的M
  1. P和M何时创建
  • P创建
  • 在确定了P的最大数量n后,运行时系统会根据这个数量创建n个P
  • M创建
  • 没有足够的M来关联P并运行其中的可运行的G,比如所有的M此时都阻塞住了,而P中海油很多就绪任务,就会去寻找空闲的M,而没有空闲的,就会去创建新的M


1.2.2.2 go func()调度流程


640.jpg


1.2.2.3 注意


  • 协程与线程之间M:N的映射关系

640.jpg


1.2.3 注意


  • 主协程退出后,其它协程也会退出
  • 可增长的栈
  • OS(操作系统)线程一般都有固定的栈内存(通常为2MB),一个goroutine的栈在其生命周期开始时只有很小的栈(典型情况是2KB),goroutine的栈不是固定的,可以按需增大和缩小,goroutine的栈大小限制可以达到1GB。
  • 在go中,线程是运行goroutine的实体,调度器的功能是把可运行的goroutine分配到工作线程上
  • 在go中,一个goroutine最多占用CPU 10ms,防止其它goroutine被饿死
  • 可以自己建立goroutine池,来有效控制goroutine数量


1.2.4 特点


  • 占用内存更小(几kb)
  • 调度更灵活(runtime调度)


1.3 并发安全和锁


1.3.1 背景

在Go代码中可能会存在多个goroutine同时操作一个资源(临界区),这个时候会发生竞态问题,所以需要保证其安全。

1.3.2 锁类型


  • 互斥锁
  • 互斥锁是一种常用的控制共享资源访问的方法,能够保证同时只有一个goroutine可以访问共享资源。
  • 使用


// go语言使用sync包的Mutex类型来实现互斥锁
var lock sync.Mutex // 定义变量
lock.Lock() // 加锁
lock.Unlock() // 解锁
  • 读写互斥锁
  • 读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其它的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其它的goroutine无论是获取读锁还是写锁都会等待
  • 使用


// go语言使用sync包中的RWMutex类型来实现读写锁
var rwlock sync.RWMutex // 定义锁变量
rwlock.Lock() // 加写锁
rwlock.Unlock() // 解写锁
rwlock.RLock() // 加读锁
rwlock.RUnlock() // 解读锁

二、常用标准库


2.1 runtime


2.1.1 功能

提供和go运行时环境的互操作


2.1.2 常用


  1. runtime.Gosched()

使当前go协程放弃处理器,以让其它go协程运行,当前go协程未来会恢复执行

  1. runtime.Goexit()

终止调用它的go协程,其他go协程不会受影响,Goexit()会在终止该go协程前执行所有defer的函数

  1. runtime.GOMAXPROCS(n int)

确定需要使用多少个OS线程来同时执行Go代码,默认值是机器上的CPU核心数。


2.2 time


2.2.1 功能


提供了时间的显示和测量用的函数


2.2.2 常用


  1. Time相关


(1)func Now() Time

功能:获取当前时间


常用

now := time.Now() // 获取当前时间
year := now.Year() // 获取年
month := now.Month() // 获取月
day := now.Day() // 获取日
hour := now.Hour() // 获取小时
minute := now.Minute() // 获取分钟
second := now.Second() // 获取秒
timestamp1 := now.Unix() // 获取时间戳
timestamp2 := now.UnixNano() // 获取纳秒时间戳

(2)func Unix(sec int64, nsec int64) Time


创建一个本地时间,例如time.Unix(timestamp, 0) // 将某个时间戳转换为时间格式;sec和nsec表示的Unix时间(从January 1, 1970 UTC至该时间的秒数和纳秒数)


(3)func (t Time) Equal(u Time) bool

判断两个时间是否相同(会考虑时区的影响)

(4) func (t Time) Add(d Duration) Time

返回时间点t+d

(5)func (t Time) Sub(u Time) Duration

返回一个时间段t-u

(6)func (t Time) Before(u Time) bool

如果t代表的时间点在u之前,返回真;否则返回假。

(7)func (t Time) After(u Time) bool

如果t代表的时间点在u之后,返回真;否则返回假。

(8) ……

  1. time包中定义的时间间隔Duration


time.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间,以纳秒为单位。


常量


(1)time.Nanosecond或time.Duration // 1纳秒

(2)time.Microsecond // 1微妙

(3)time.Millsecond // 1毫秒

(4)time.Second // 1秒

(5)time.Minute // 1分钟

(6)time.Hour // 1小时

  1. Timer相关


Timer类型表示单次时间事件,当Timer到期时,当时的时间会被发送给C,除非Timer是被AfterFunc函数创建的

操作


(1)func NewTimer(d Duration) *Timer

NewTimer创建一个Timer,它会在最少过去时间段d后到期(时间到了,执行只执行1次)

(2)unc AfterFunc(d Duration, f func()) *Timer

AfterFunc另起一个go程等待时间段d过去,然后调用f

(3)func (t *Timer) Reset(d Duration) bool

Reset使t重新开始计时,(本方法返回后再)等待时间段d过去后到期。如果调用时t还在等待中会返回真;如果t已经到期或者被停止了会返回假。

(4)func (t *Timer) Stop() bool

Stop停止Timer的执行。


  1. Ticker相关


Ticker保管一个通道,并每隔一段时间向其传递"tick"。


操作


(1)func NewTicker(d Duration) *Ticker

NewTicker返回一个新的Ticker,该Ticker包含一个通道字段,并会每隔时间段d就向该通道发送当时的时间(时间到了会多次执行)

(2)func (t *Ticker) Stop()

top关闭一个Ticker。在关闭后,将不会发送更多的tick信息。

  1. 其它


(1)func Sleep(d Duration)

Sleep阻塞当前go程至少d代表的时间段。d<=0时,Sleep会立刻返回。

(2)func After(d Duration) <-chan Time

After会在另一线程经过时间段d后向返回值发送当时的时间。等价于NewTimer(d).C。

(3)func Tick(d Duration) <-chan Time

Tick是NewTicker的封装,只提供对Ticker的通道的访问。如果不需要关闭Ticker,本函数就很方便。


2.3 sync


2.3.1 功能


sync包提供了基本的同步单元,如互斥锁


2.3.2 常用


  1. type Mutex
  • 互斥锁
  1. type RWMutex
  • 读写互斥锁
  1. type Once

Once是只执行一次动作的对象。

  • func (o *Once) Do(f func())// Do方法当且仅当第一次被调用时才执行函数f。
  1. type WaitGroup

WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。- 方法


(1)func (wg *WaitGroup) Add(delta int)

Add方法向内部计数加上delta

(2)func (wg *WaitGroup) Done()

Done方法减少WaitGroup计数器的值,应在线程的最后执行。

(3)func (wg *WaitGroup) Wait()

Wait方法阻塞直到WaitGroup计数器减为0。

(4)例子


var wg sync.WaitGroup
func hello() {
    defer wg.Done()
    fmt.Println("Hello Goroutine!")
}
func main() {
    wg.Add(1)
    go hello() // 启动另外一个goroutine去执行hello函数
    fmt.Println("main goroutine done!")
    wg.Wait()
}

2.4 fmt


2.4.1 功能


fmt包实现了类似C语言printf和scanf的格式化I/O。主要分为向外输出内容和获取输入内容两大部分

2.4.2 常用


2.4.2.1 向外输出


  1. Print系列

Print系列函数会将内容输出到系统的标准输出

(1)func Print(a ...interface{}) (n int, err error)

直接输出内容

(2)func Printf(format string, a ...interface{}) (n int, err error)

支持格式化输出字符串

(3)func Println(a ...interface{}) (n int, err error)

会在输出内容的结尾添加一个换行符

  1. Fprint系列

Fprint系列函数会将内容输出到一个io.Writer接口类型的变量w中,通常用这个函数往文件中写内容

(1)func Fprint(w io.Writer, a ...interface{}) (n int, err error) (2)func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) (3)func Fprintln(w io.Writer, a ...interface{}) (n int, err error)

  1. Sprint系列

Sprint系列函数会把传入的数据生成并返回一个字符串

(1)func Sprint(a ...interface{}) string (2)func Sprintf(format string, a ...interface{}) string (3)func Sprintln(a ...interface{}) string

  1. func Errorf(format string, a ...interface{}) error


Errorf函数根据format参数生成格式化字符串并返回一个包含该字符串的错误。


2.4.2.2 获取输入


  1. Scan系列

(1)func Scan(a ...interface{}) (n int, err error)

Scan从标准输入扫描文本,读取由空白符分隔的值保存到传递给本函数的参数中,换行符视为空白符。

(2)func Scanf(format string, a ...interface{}) (n int, err error)

Scanf从标准输入扫描文本,根据format参数指定的格式去读取由空白符分隔的值保存到传递给本函数的参数中。

(3)func Scanln(a ...interface{}) (n int, err error)

Scanln类似Scan,它在遇到换行时才停止扫描。最后一个数据后面必须有换行或者到达结束位置。

  1. Fscan系列

(1)func Fscan(r io.Reader, a ...interface{}) (n int, err error)

Fscan从r扫描文本,将成功读取的空白分隔的值保存进成功传递给本函数的参数。

(2)func Fscanln(r io.Reader, a ...interface{}) (n int, err error)

Fscanln类似Fscan,但会在换行时才停止扫描。

(3)func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)

Fscanf从r扫描文本,根据format 参数指定的格式将成功读取的空白分隔的值保存进成功传递给本函数的参数。

  1. Sscan系列

(1)func Sscan(str string, a ...interface{}) (n int, err error)

Sscan从字符串str扫描文本,将成功读取的空白分隔的值保存进成功传递给本函数的参数。

(2)func Sscanln(str string, a ...interface{}) (n int, err error)

Sscanln类似Sscan,但会在换行时才停止扫描。最后一个条目后必须有换行或者到达结束位置。

(3)func Sscanf(str string, format string, a ...interface{}) (n int, err error)

Sscanf从字符串str扫描文本,根据format 参数指定的格式将成功读取的空白分隔的值保存进成功传递给本函数的参数。


2.4.3 格式化占位符


  1. 通用占位符
- %v  值的默认格式表示
 - %+v  类似%v,但输出结构体时会添加字段名
 - %#v  值的Go语法表示
 - %T 打印值的类型
 - %% 百分号
  1. 布尔型
- %t  true或false
  1. 整形
- %b  表示为二进制
 - %c 该值对应的unicode码值
 - %d 表示为十进制
 - %o 表示为八进制
 - %x 表示为十六进制,使用a-f
 - %X 表示为十六进制,使用A-F
 - %U 表示为Unicode格式:U+1234,等价于”U+%04X”
 - %q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
  1. 浮点数和复数
- %b  无小数部分、二进制指数的科学计数法,如-123456p-78
 - %e 科学计数法,如-1234.456e+78
 - %E 科学计数法,如-1234.456E+78
 - %f 有小数部分但无指数部分,如123.456
 - %F 等价于%f
 - %g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
 - %G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)
  1. 字符串和[]byte
- %s  直接输出字符串或者[]byte
 - %q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
 - %x 每个字节用两字符十六进制数表示(使用a-f
 - %X 每个字节用两字符十六进制数表示(使用A-F
  1. 指针
- %p  表示为十六进制,并加上前导的0x
  1. 宽度标识符
- %f  默认宽度,默认精度
 - %9f  宽度9,默认精度
 - %.2f 默认宽度,精度2
 - %9.2f  宽度9,精度2
 - %9.f 宽度9,精度0
  1. 其它flag
- ’+’ 总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义);
 - ’ ‘  对数值,正数前加空格而负数前加负号;对字符串采用%x或%X时(% x或% X)会给各打印的字节之间加空格
 - ’-’  在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐);
 - ’#’  八进制数前加0(%#o),十六进制数前加0x(%#x)或0X(%#X),指针去掉前面的0x(%#p)对%q(%#q),对%U(%#U)会输出空格和单引号括起来的go字面值;
 - ‘0’  使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面;

2.5 bufio


  • bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。

2.6  bytes


  • bytes包实现了操作[]byte的常用函数。

2.7 compress系列


  • 一系列与压缩相关的内容

2.8 crypto系列


  • 一些列加解密相关内容

2.9 encoding系列


  • encoding包定义了供其它包使用的可以将数据在字节水平和文本表示之间转换的接口

2.10 flag


  • flag包实现了命令行参数的解析

2.11 io


  • io包提供了对I/O原语的基本接口。本包的基本任务是包装这些原语已有的实现(如os包里的原语),使之成为共享的公共接口,这些公共接口抽象出了泛用的函数并附加了一些相关的原语的操作。

2.12 log


  • log包实现了简单的日志服务。

2.13 math


  • math包提供了基本的数学常数和数学函数。

2.14 net系列


  • 提供了网络通讯相关的内容

2.15 os


  • os包提供了操作系统函数的不依赖平台的接口,例如可进行文件操作。

2.16 reflect


  • reflect包实现了运行时反射,允许程序操作任意类型的对象。

2.17 strings


  • strings包实现了用于操作字符的简单函数。

2.18 其它


库太多了,可直接查文档http://doc.golang.ltd/

相关文章
|
4天前
|
前端开发 Java 编译器
【前端学java】如何从前端视角快速学习Maven
【8月更文挑战第12天】如何从前端视角快速学习Maven
12 2
【前端学java】如何从前端视角快速学习Maven
|
6天前
|
前端开发 JavaScript API
前端框架Vue------>第二天学习(1)插槽
这篇文章介绍了Vue框架中插槽(slot)的概念和用法,以及如何在组件中使用自定义事件进行父子组件间的通信。
前端框架Vue------>第二天学习(1)插槽
|
6天前
|
JSON 前端开发 JavaScript
前端框架Vue------>第一天学习(3)
这篇文章是关于使用Vue框架进行前端开发的教程,重点介绍了如何使用Axios实现异步通信和表单输入的双向数据绑定。
前端框架Vue------>第一天学习(3)
|
6天前
|
前端开发 JavaScript API
前端框架Vue------>第一天学习(2) v-if
这篇文章介绍了Vue框架中条件渲染的用法,包括`v-if`、`v-else-if`指令的使用,以及列表渲染和事件监听的基本实现。
前端框架Vue------>第一天学习(2) v-if
|
1天前
|
安全 Java Go
探索Go语言在高并发环境中的优势
在当今的技术环境中,高并发处理能力成为评估编程语言性能的关键因素之一。Go语言(Golang),作为Google开发的一种编程语言,以其独特的并发处理模型和高效的性能赢得了广泛关注。本文将深入探讨Go语言在高并发环境中的优势,尤其是其goroutine和channel机制如何简化并发编程,提升系统的响应速度和稳定性。通过具体的案例分析和性能对比,本文揭示了Go语言在实际应用中的高效性,并为开发者在选择合适技术栈时提供参考。
|
5天前
|
运维 Kubernetes Go
"解锁K8s二开新姿势!client-go:你不可不知的Go语言神器,让Kubernetes集群管理如虎添翼,秒变运维大神!"
【8月更文挑战第14天】随着云原生技术的发展,Kubernetes (K8s) 成为容器编排的首选。client-go作为K8s的官方Go语言客户端库,通过封装RESTful API,使开发者能便捷地管理集群资源,如Pods和服务。本文介绍client-go基本概念、使用方法及自定义操作。涵盖ClientSet、DynamicClient等客户端实现,以及lister、informer等组件,通过示例展示如何列出集群中的所有Pods。client-go的强大功能助力高效开发和运维。
24 1
|
5天前
|
前端开发 JavaScript
前端网站学习大全
这篇文章提供了前端网站开发学习的资源大全,涵盖了HTML常用标签和CSS常用样式,以及如何使用`<meta>`标签提供页面元信息和`lang`属性定义内容语言等基础知识。
前端网站学习大全
|
6天前
|
前端开发 JavaScript
前端框架Vue------>第三天学习(1)
这篇文章介绍了Vue框架的组件基础和计算属性的概念,通过示例代码展示了如何定义可复用的Vue组件和使用计算属性来声明性地描述依赖其他值的数据。
|
5天前
|
算法 NoSQL 中间件
go语言后端开发学习(六) ——基于雪花算法生成用户ID
本文介绍了分布式ID生成中的Snowflake(雪花)算法。为解决用户ID安全性与唯一性问题,Snowflake算法生成的ID具备全局唯一性、递增性、高可用性和高性能性等特点。64位ID由符号位(固定为0)、41位时间戳、10位标识位(含数据中心与机器ID)及12位序列号组成。面对ID重复风险,可通过预分配、动态或统一分配标识位解决。Go语言实现示例展示了如何使用第三方包`sonyflake`生成ID,确保不同节点产生的ID始终唯一。
go语言后端开发学习(六) ——基于雪花算法生成用户ID
|
3天前
|
NoSQL Go Redis
Go语言中如何扫描Redis中大量的key
在Redis中,遍历大量键时直接使用`KEYS`命令会导致性能瓶颈,因为它会一次性返回所有匹配的键,可能阻塞Redis并影响服务稳定性。为解决此问题,Redis提供了`SCAN`命令来分批迭代键,避免一次性加载过多数据。本文通过两个Go语言示例演示如何使用`SCAN`命令:第一个示例展示了基本的手动迭代方式;第二个示例则利用`Iterator`简化迭代过程。这两种方法均有效地避免了`KEYS`命令的性能问题,并提高了遍历Redis键的效率。
11 0