GO语言核心30讲 实战与应用 (WaitGroup和Once,context,Pool,Map,字符编码,string包,bytes包)

news2024/11/26 20:46:18

 原站地址:Go语言核心36讲_Golang_Go语言-极客时间

一、sync.WaitGroup和sync.Once

1. sync.WaitGroup 比通道更加适合实现一对多的 goroutine 协作流程。

2. WaitGroup类型有三个指针方法:Wait、Add和Done,以及内部有一个计数器。

(1) Wait方法:阻塞当前的 goroutine,直到计数器归零。

(2) add方法:加减计数器的值。用来记录需要等待的 goroutine 的数量

(3) Done方法:对计数器的值进行减1操作。可以在需要等待的 goroutine 中,通过defer语句调用它。

3. 具体实现代码:

func coordinateWithWaitGroup() {
 var wg sync.WaitGroup    //声明WaitGroup类型,开箱即用
 wg.Add(2)                //设置要等待的goroutine数目,加到计数器里
 num := int32(0)
 fmt.Printf("The number: %d [with sync.WaitGroup]\n", num)
 max := int32(10)
 go addNum(&num, 3, max, wg.Done)  //启用第一个goroutine,结束后调用Done函数对计数器减1
 go addNum(&num, 4, max, wg.Done)  //启用第二个goroutine
 wg.Wait()    //阻塞等待计数器归0
}

4. 使用 WaitGroup注意:

(1) 计数器的值不能小于0.虽然是开箱即用,但是必须且尽早地增加其计数器的值。

(2) WaitGroup可以多次使用,但是不能跨周期使用。必须要等这个Wait方法执行结束之后,才能够开始下一个周期。

     也就是: wait执行过程中,计数器不能被其他goroutine增加,否则会panic。wait方法和add方法必须放在同一个goroutine 中。

5. sync.Once 类型说明:

(1) 向其输入一个函数参数,然后保证这个函数之后只会被执行一次。

(2) 它的Do方法只接受一个函数参数,且必须是无参数声明和结果声明的函数。

(3) 只执行一次,是针对Once值来说的。所以,有多个只需要执行一次的函数,就应该为它们每一个都分配各自的Once值。

(4) 它内部有一个名叫done的字段,通过原子操作来计数,保证只执行一次。

6. 使用 sync.Once 需要注意的事项:

(1) Do方法只会在参数函数执行结束之后,才把done字段的值变为1。如果并发地调用了Once值的Do方法,会让其中一个发生阻塞。所以:Do方法不能是太耗时的或不会终结的。

(2) 对done字段的赋值1 是在 Once内部用defer语句的方式进行的,不是交给用户操作,所以无论是逻辑错误还是panic都必然赋值1,不能再执行。所以:Do方法不能实现重试机制。

二、context.Context类型

1. Context类型也是一种非常通用的同步工具,它还可以提供一类代表上下文的信息值。

    并且这些信息的值是并发安全的,可以被传播给多个 goroutine。

    Context还能传达撤销信号。

2. Context类型的值是可以繁衍的,可以通过一个Context值产生出任意个子值。

     产生子值时需要输入父值的Context,子值携带父值的数据。

     所有的Context值共同构成了代表了上下文全貌的树形结构,树根是一个已经在context包中预定义好的Context值,它是全局唯一的。

3. context包中包含了四个用于产生Context值的函数: (撤销具体意义见下面4)

(1) WithCancel:产生一个可撤销的Context子值,和一个用于触发撤销信号的函数。

(2) WithDeadline:产生会定时撤销的Context子值

(3) WithTimeout:产生会定时撤销的Context子值

(4) WithValue:产生会携带额外数据的Context子值

4. “撤销”一个Context值意味着什么?

    Context类型的Done方法会返回一个接收通道,这个接收通道的用途并不是传递元素值,而是让调用方去接收 当前Context值已“撤销”的信号

    一旦Context值被撤销,接收通道就会立即被关闭。对于一个未包含任何元素值的通道来说,它的关闭会使针对它的接收操作结束

    代码示例如下:

func coordinateWithContext() {
 total := 12
 var num int32
 //1. 获得context子值和撤销函数
 cxt, cancelFunc := context.WithCancel(context.Background()) 
 for i := 1; i <= total; i++ {
  go addNum(&num, i, func() {
   if atomic.LoadInt32(&num) == int32(total) {
    cancelFunc() //2. 全部goroutine运行完毕,执行撤销函数。
   }})
 }
 <-cxt.Done() //3. 撤销函数执行完毕,cxt被撤销,cxt.Done()返回的通道被关闭。
 //4. 从被关闭的通道做接收操作,代码会解除阻塞,往下执行。
 fmt.Println("End.")
}

5. “撤销”的应用场景还包括: HTTP 请求被终止后的响应,SQL 指令被取消后的处理。

6. 当父值context的撤销函数被调用后,Context值会先关闭它内部的接收通道,然后向它所有子值传达撤销信号。

7. WithValue函数在产生含数据的Context值时,需要三个输入参数,即:父值、键和值。

    含数据Context值并不是用字典来存储键和值,只是把键和值简单地存储在自己相应的字段中而已。

8. Context类型的Value方法就是被用来获取数据的。

    它先判断给定的键,是否与当前值中存储的键相等,如果相等就把返回;否则就到其父值中继续查找。

    如果其父值中仍然未存储相等的键,那么就沿着上下文根节点的方向一路查找下去。

9. Context接口没有提供改变数据的方法。只能通过在上下文树中添加含数据的Context值来存储新的数据,以及通过撤销此种值的父值来丢弃掉不需要的数据

三、临时对象池sync.Pool

1. sync.Pool类型,被称为临时对象池,临时对象。的意思是:不需要持久使用的某一类值。

2. sync.Pool类型只有两个方法——Put和Get。

    Get方法会从当前的池中删除掉一个值,然后把这个值作为结果返回。

    如果Get方法没能获得值,就会使用New字段创建一个新值。

    New字段的实际值需要我们在初始化临时对象池的时候就给定。

3. 为什么说临时对象池中的值会被及时地清理掉?

(1) sync包有一个私有的全局变量,汇总了所有临时对象池,称为池汇总列表

(2) 临时对象池的Put方法或Get方法,第一次被调用的时候,这个池就会被添加到池汇总列表中。

(3) GO 执行垃圾回收时,会执行池清理函数。函数通过访问池汇总列表,就能访问到被使用过的临时对象池。

(4) 当临时对象没有被其他代码引用时,就会被清理掉。

4. 临时对象池的数据结构如何?

    临时对象池是一个多层的数据结构,顶层称为本地池列表,是一个数组。

    本地池列表长度与 Go 调度器中的 P 的数量相同,列表每个元素(本地池) 关联一个P。

    每个本地池包含三个组建:

(1) 私有临时对象列表:只提供给当前P关联的G访问

(2) 共享临时对象列表:允许其他P关联的G访问

(3) 互斥量 sync.Mutex:被其他P关联的G访问时,这里用于加锁保护。

5. Get方法获取临时对象池的顺序

四、并发安全字典sync.Map

1. sync.Map 与 使用原生map和互斥锁 的方案相比,sync.Map效率更高,因为尽量使用原子操作

2. 使用原子操作,于是就对 key 有数据类型的要求,不能是函数、字典和切片。

3. sync.Map 的key 类型是键静态类型是interface{},动态类型只有在程序运行时才能够确定。

    这就导致键类型有可能不符合要求。所以需要显式地检查键值的实际类型

4. 可以调用 reflect.TypeOf函数 得到一个键值对应的反射类型值(即:reflect.Type类型的值),然后调用Comparable方法,得到判断结果。

5. 保证并发安全字典中的键和值的类型正确,方案一

    自己重新声明一个结构体类型,把 sync.Map 封装进去,然后声明的方法里写死只接受特定类型的 key 和 value

     适用场景: 可以完全确定键和值的具体类型。

     缺点:对类型限定得比较死。

6. 保证并发安全字典中的键和值的类型正确,方案二

    在方案一的前提下,使用 interface{} 作为 key 和 value 的类型,然后 用 reflect.TypeOf函数做类型判断。

func (cMap *ConcurrentMap) Load(key interface{}) (value interface{}, ok bool) {
 if reflect.TypeOf(key) != cMap.keyType {
  return
 }
 return cMap.m.Load(key)
}

     适用场景: 不能确定键和值的具体类型。

     缺点:效率相对低。

7. 并发字典是如何避免使用锁的?

(1) 大致上是让读操作尽量使用原子操作,提高读取速度。

(2) 并发字典内部使用了两个原生的map作为存储介质,一个只读map,一个会被修改的脏map。 

     两个map的数据有差异,在脏map被修改后就不一致了。

(3)读取数据时,先从只读map里读,并使用原子操作的方式读取数据。

     读不到数据时,才从脏map里读数据。

(4) 如果频繁从脏map里读数据,那就把脏map变成只读map。然后在下一次写操作时,把只读map复制,生成新的脏map。

(5) 只读map:键值对可改变,但不能增减键。 删除键时,会被标记,而不会直接删除。

     脏map:键值对可改变,可以增减。删除键时,直接删除。会在锁保护状态下进行。

五、unicode与字符编码

1. Go 语言的源码文件必须使用 UTF-8 编码格式进行存储

2. string类型的值被转换为[]rune的时候,字符串会被拆分成 Unicode 字符序列。

    string转换会被转成怎样的字符序列,就是字符编码。

3. UTF-8 编码方案,用一个字节表示英文,用三个字节表示中文。

4. string类型的值会由若干个 Unicode 字符组成,每个 Unicode 字符由一个rune类型的值来承载。 : []rune

   字符在底层都会被转换为 UTF-8 编码值,UTF-8 编码值又以字节序列形式存储:[]byte

5. 使用 for range语句 遍历字符串的时候注意:

(1) for range语句会把被遍历的字符串值拆成一个字节序列,然后再找出这个字节序列包含的Unicode 字符,然后输出 Unicode 字符 rune。

(2) for range 语句输出的两个迭代变量,第一个是索引值。这个索引值不总是循环+1的,如果遇到unicode字符,会+3 

str := "Go爱好者"
for i, c := range str {
 //%q是输出unicode字符rune,%x是输出字节序列
 fmt.Printf("%d: %q [% x]\n", i, c, []byte(string(c)))
}

输出结果:
0: 'G' [47]
1: 'o' [6f]
2: '爱' [e7 88 b1]
5: '好' [e5 a5 bd]
8: '者' [e8 80 85]

六、strings包与字符串操作

1. string值在内部持有一个指针值,这个指针值指向一个字节数组。

    string值进行拷贝的话,副本也是指向同一个字节数组。

    修改string内容的话,其实是修改了这个指针的指向。创建了新的字节数组,原来的字节数组被丢弃不用。当引用不存在之后,会被系统清除掉。

2. strings包中有两个重要类型:Builder和Reader。 Builder用于构建字符串,Reader用于读取字符串。

3. Builder与string值相比,Builder的优势体现在字符串拼接方面。它不需要像string那样每次修改都创建新的字节数组,它可以在原字节数组基础上进行扩容,减少内存分配和内容拷贝的次数。

4. Builder扩容的逻辑,就和切片扩容一模一样。

5. Reader读取字符串的高效体现在 会保存对已读字节数的计数上。它代表着下一次读取的起始索引位置,可以更加灵活地进行字符串读取。

七、bytes包与字节串操作

1. bytes包和strings包在 API 方面非常的相似,从函数功能上讲,差别微乎其微。区别是:

    strings包面向的是 Unicode 字符和经过 UTF-8 编码的字符串。

2. bytes.Buffer类型的用途主要是作为 字节序列的缓冲区。bytes.Buffer是集读、写功能于一身的数据类型。

3. bytes.Buffer的len()方法获取的长度是未读内容的长度,而不是已存内容的总长度。

    cap()方法获取的是内部字节切片的容量,只与之前的写操作有关,并会随着内容的写入而不断增长。

4. bytes.Buffer 和 string.Builder都使用已读字节数的计数机制。但区别是这个已读计数不能直接获得。

    已读计数非常重要,在读取内容、写入内容、截断内容、读回退、重置内容、导出内容和获取长度时,都需要它。

5. bytes.Buffer 存在 非标准读取的操作问题。比如:

    通过 unreadBytes := buffer1.Bytes()  获得未读字节 切片 unreadBytes

    这个unreadBytes 和 buffer1 在共用着底层的一个字节数组。在buffer1内容被添加之后,只要对 unreadBytes 进行切片操作,就能非标准地获得 未读的内容。甚至对切片内容进行修改。

    最彻底的避免问题的办法是,在传出切片值时做隔离。比如先做深度拷贝,再把副本传出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1671363.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【报错合集】完美解决“虚拟机使用的是此版本 VMware Workstation 不支持的硬件版本”

文章目录 解决方案&#xff1a;更改设置的硬件版本 今天我需要将别人的虚拟机克隆到我的VMware Workstation上运行&#xff0c;结果发生了以下的错误&#xff1a; 刚开始以为是VMware Workstation的版本问题太低导致的&#xff0c;所以我删除了原来的那个版本&#xff0c;下载…

51cto已购买的视频怎么下载到本地

你是否曾在学习51CTO的精品课程时&#xff0c;希望可以随时随地无网络干扰地进行学习&#xff0c;或是想要将这些已购买的课程永久珍藏&#xff1f;今天&#xff0c;你的愿望将要实现。我们将向你揭示如何轻松地将已购买的51CTO视频下载到本地&#xff0c;让学习的路上再也没有…

企业邮箱域名是什么?怎么注册一个企业邮箱域名?

企业邮箱域名是什么&#xff1f;企业邮箱域名是企业申请的专属域名&#xff0c;绑定专属的邮箱域名&#xff0c;能够在发送邮件时提高品牌识别性、专业性和宣传效果。那么&#xff0c;我们该怎么注册一个企业邮箱域名呢&#xff1f;本文将为你详细介绍。 一、企业邮箱域名是什…

本地搭建各大直播平台录屏服务结合内网穿透工具实现远程管理录屏任务

文章目录 1. Bililive-go与套件下载1.1 获取ffmpeg1.2 获取Bililive-go1.3 配置套件 2. 本地运行测试3. 录屏设置演示4. 内网穿透工具下载安装5. 配置Bililive-go公网地址6. 配置固定公网地址 本文主要介绍如何在Windows系统电脑本地部署直播录屏利器Bililive-go&#xff0c;并…

内网环境安装使用DBeaver使用第一天

之前一直使用navicat&#xff0c;现在出于某种原因不让使用了&#xff0c;于是上手了这个工具&#xff0c;说实话&#xff0c;真的&#xff0c;但是必须要用。 首先安装的时候&#xff0c;必须要选择MySQL驱动&#xff0c;如果外网直接选择以后就可以下载了&#xff0c;内网需…

【MySQL复合查询】

文章目录 一、基本的使用案例二、多表查询三、自连接四、子查询4.1单行子查询4.2多行子查询in关键字all关键字any关键字 4.3多列子查询4.4 在from子句中使用子查询 解决多表问题的本质五、合并查询1.union2.union all 一、基本的使用案例 注明&#xff1a;以下案例使用的均为一…

Docker-compose部署TRX节点

1、编写Dockerfile rootubuntu:~# mkdir /data/docker-compose/trx -p rootubuntu:~# cd /data/docker-compose/trx/ rootubuntu:/data/docker-compose/trx# ls rootubuntu:/data/docker-compose/trx# vim Dockerfile rootubuntu:/data/docker-compose/trx# cat Dockerfile FR…

鲁棒控制问题描述

复杂的合成问题成为一个具有特殊结构控制器的设计问题。 H无穷范数&#xff08;H∞ norm&#xff09;&#xff1a;对于线性时不变&#xff08;LTI&#xff09;系统&#xff0c;H∞范数通常定义为系统频率响应的最大幅值。换句话说&#xff0c;它是系统传递函数在复平面单位圆上…

基于SSM的“图书仓储管理系统”的设计与实现(源码+数据库+文档)

基于SSM的“图书仓储管理系统”的设计与实现&#xff08;源码数据库文档) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 系统登录页面 人员管理信息页面 添加人员信息页…

公司申请增加公众号数量

一般可以申请多少个公众号&#xff1f;众所周知&#xff0c;在2013年前后&#xff0c;公众号申请是不限制数量的&#xff0c;后来企业开始限制申请50个&#xff0c;直到2018年的11月tx又发布&#xff0c;其中个人主体可申请公众号由2个调整为1个&#xff0c;企业主体由50个调整…

[C/C++] -- 搜索迷宫路径

DFS&#xff08;深度优先搜索&#xff09;和BFS&#xff08;广度优先搜索&#xff09;是两种常用的图遍历算法&#xff0c;它们在搜索图或树中的节点时有着不同的策略和特点。 深度优先搜索 (DFS): 在DFS中&#xff0c;从起始节点开始&#xff0c;沿着一条路径尽可能深地搜索&a…

Footprint Analytics 与 Core Chain 达成战略合作

​ 领先的区块链数据解决方案提供商 Footprint Analytics 与比特币驱动、EVM 兼容的 Layer 1 区块链 Core Chain 宣布达成战略合作。此次合作旨在将 Footprint Analytics 的前沿数据解决方案与 Core Chain 的区块链基础设施相结合&#xff0c;共同引领区块链领域的创新发展。 …

class常量池、运行时常量池和字符串常量池的关系

类常量池、运行时常量池和字符串常量池这三种常量池&#xff0c;在Java中扮演着不同但又相互关联的角色。理解它们之间的关系&#xff0c;有助于深入理解Java虚拟机&#xff08;JVM&#xff09;的内部工作机制&#xff0c;尤其是在类加载、内存分配和字符串处理方面。 类常量池…

【文献阅读】企业ESG表现与创新——来自A股上市公司的证据

企业ESG表现与创新——来自A股上市公司的证据 1.引言 第一段——背景介绍 可持续发展 碳达峰、碳中和 ESG既是从微观层面解决全球性社会问题的必要&#xff0c;也是实现我国经济转型、促进高质量发展的有效手段。 2017.12证监会&#xff1a;重点排污企业的环境披露 2021.6证监…

【C++】string类的使用④(字符串操作String operations || 常量成员Member constants)

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; STL || C 目录 前言&#x1f525;字符串操作&#xff08;String operations&#xff09;c_strdataget_allocatorcopyfindrfindfind_first_offind_last_offind_first_not_offind_last_not…

编码器介绍与应用

一.概述 1.编码器 编码器&#xff0c;是一种用来测量机械旋转或位移的传感器。这种传感器能够测量机械部件在旋转或直线运动时的位移位置或速度等信息&#xff0c;并将其转换成一系列电信号。其可和电机组装到一起用&#xff0c;反馈电机方向、转换角度的&#xff0c;然后电机…

MongoDB和AI 赋能行业应用:制造业和汽车行业

欢迎阅读“MongoDB和AI 赋能行业应用”系列的第一篇。 本系列重点介绍AI应用于不同行业的关键用例&#xff0c;涵盖制造业和汽车行业、金融服务、零售、电信和媒体、保险以及医疗保健行业。 随着人工智能&#xff08;AI&#xff09;在制造业和汽车行业的集成&#xff0c;传统…

求四个整数中的最大值(函数)(C语言)

一、N-S流程图&#xff1b; 二、运行结果&#xff1b; 三、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int a, b, c, d, max;//获取用户输入的数据&#xff1b;printf("请输入4个整数&#x…

内部开发平台如何赋能开发人员与业务

一个厨师只有具备烹饪美食的技能与经验&#xff0c;并且在设备、工具齐全的餐厅里才能发挥他的才能。交响乐团需要正确的乐器、指挥家和舞台才能演奏初美妙的音乐。 而在软件开发的世界&#xff0c;开发人员需要最好的工具包和开发环境来设计开发他们的软件项目。这个环境就被…

Mysql数据类型设计思考

一、Mysql数据类型设计规范 1.1 选择更小的数据类型 一般情况下&#xff0c;在满足存储要求的基础上&#xff0c;尽量选择小的存储类型。例如&#xff1a;存储0~200&#xff0c;tinyint和bigint都可以存储&#xff0c;那么选择tinyint。原因&#xff1a;越小的数据类型运算速…