前端工程师学习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/

相关文章
|
19小时前
|
JSON 前端开发 Go
lucky - go 语言实现的快速开发平台
go 语言实现的快速开发平台,自动生成crud代码,前端页面通过json配置,无需编写前端代码。
6 0
|
1天前
|
存储 Java Go
Go 语言切片如何扩容?(全面解析原理和过程)
Go 语言切片如何扩容?(全面解析原理和过程)
12 2
|
1天前
|
负载均衡 Go 调度
使用Go语言构建高性能的Web服务器:协程与Channel的深度解析
在追求高性能Web服务的今天,Go语言以其强大的并发性能和简洁的语法赢得了开发者的青睐。本文将深入探讨Go语言在构建高性能Web服务器方面的应用,特别是协程(goroutine)和通道(channel)这两个核心概念。我们将通过示例代码,展示如何利用协程处理并发请求,并通过通道实现协程间的通信和同步,从而构建出高效、稳定的Web服务器。
|
1天前
|
算法 Go 分布式数据库
构建高可用的分布式数据库集群:使用Go语言与Raft共识算法
随着数据量的爆炸式增长,单一数据库服务器已难以满足高可用性和可扩展性的需求。在本文中,我们将探讨如何使用Go语言结合Raft共识算法来构建一个高可用的分布式数据库集群。我们不仅会介绍Raft算法的基本原理,还会详细阐述如何利用Go语言的并发特性和网络编程能力来实现这一目标。此外,我们还将分析构建过程中可能遇到的挑战和解决方案,为读者提供一个完整的实践指南。
|
1天前
|
消息中间件 Go API
基于Go语言的微服务架构实践
随着云计算和容器化技术的兴起,微服务架构成为了现代软件开发的主流趋势。Go语言,以其高效的性能、简洁的语法和强大的并发处理能力,成为了构建微服务应用的理想选择。本文将探讨基于Go语言的微服务架构实践,包括微服务的设计原则、服务间的通信机制、以及Go语言在微服务架构中的优势和应用案例。
|
2天前
|
安全 测试技术 数据库连接
使用Go语言进行并发编程
【5月更文挑战第15天】Go语言以其简洁语法和强大的并发原语(goroutines、channels)成为并发编程的理想选择。Goroutines是轻量级线程,由Go运行时管理。Channels作为goroutine间的通信机制,确保安全的数据交换。在编写并发程序时,应遵循如通过通信共享内存、使用`sync`包同步、避免全局变量等最佳实践。理解并发与并行的区别,有效管理goroutine生命周期,并编写测试用例以确保代码的正确性,都是成功进行Go语言并发编程的关键。
|
2天前
|
数据采集 监控 Java
Go语言并发编程:Goroutines和Channels的详细指南
Go语言并发编程:Goroutines和Channels的详细指南
11 3
|
2天前
|
数据采集 人工智能 搜索推荐
快速入门:利用Go语言下载Amazon商品信息的步骤详解
本文探讨了使用Go语言和代理IP技术构建高效Amazon商品信息爬虫的方法。Go语言因其简洁语法、快速编译、并发支持和丰富标准库成为理想的爬虫开发语言。文章介绍了电商网站的发展趋势,如个性化推荐、移动端优化和跨境电商。步骤包括设置代理IP、编写爬虫代码和实现多线程采集。提供的Go代码示例展示了如何配置代理、发送请求及使用goroutine进行多线程采集。注意需根据实际情况调整代理服务和商品URL。
快速入门:利用Go语言下载Amazon商品信息的步骤详解
|
2天前
|
存储 编译器 Go
Go语言学习12-数据的使用
【5月更文挑战第5天】本篇 Huazie 向大家介绍 Go 语言数据的使用,包含赋值语句、常量与变量、可比性与有序性
45 6
Go语言学习12-数据的使用
|
存储 JSON API
Go语言:RESTful API 服务,急速入门!
REST即表述性状态传递(英文:Representational State Transfer,简称REST),它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性
417 0
Go语言:RESTful API 服务,急速入门!