GO语言核心30讲 实战与应用 (第二部分)

news2024/10/5 14:17:58

 原站地址: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]

6. strings包与字符串操作

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

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

相关文章

从零开始搭建Ubuntu CTF-pwn环境

下面就将介绍如何从零搭建一个CTF-pwn环境&#xff08;由于学习仍在进行&#xff0c;故一些环境如远程执行环境还没有搭建的经历&#xff0c;如今后需要搭建&#xff0c;会在最后进行补充&#xff09; 可以在ubuntu官方网站上下载最新的长期支持版本:(我下载的是22.04版本) h…

【研发日记】Matlab/Simulink避坑指南(十二)——Initialize Function执行Bug

文章目录 前言 背景介绍 问题描述 分析排查 解决方案 总结归纳 前言 见《研发日记&#xff0c;Matlab/Simulink避坑指南(七)——数据溢出钳位Bug》 见《研发日记&#xff0c;Matlab/Simulink避坑指南(八)——else if分支结构Bug》 见《研发日记&#xff0c;Matlab/Simuli…

uni-app(四):原生插件开发(Android)

原生插件开发 原生插件开发module1.创建模块2.解决报错3.修改依赖4.编写插件代码5.添加插件配置6.引入模块7.调用插件代码8.运行 component1.创建模块2.解决报错3.修改依赖4.编写插件代码5.添加插件配置6.引入模块7.调用插件代码8.运行 原生插件开发 主要分为两类扩展: Module:…

UE4 3D文字自动换行

效果&#xff1a;3D文字超过5位自动换行 1.随意输入一段字符串测试&#xff0c;创建string临时变量&#xff0c;用于迭代存储字符串 2.当字符串遍历至第“换行长度”&#xff08;我这里是5&#xff09;位时&#xff0c;附加一次空行

修改ollama模型文件下载位置

修改ollama模型文件下载位置。你如果不改这个东西&#xff0c;所有的模型文件都会下到c盘&#xff0c;土豪随意。 这里修改环境变量&#xff1a; OLLAMA_MODELS将这个环境变量设置为你想存放的路径。然后重启电脑&#xff01;

AI大模型探索之路-训练篇20:大语言模型预训练-常见微调技术对比

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…

javaFor循环-打印九九乘法表

虽然所有循环结构都可以用while或者do...while表示&#xff0c;但java提供了另一种循环语句--for循环&#xff0c;使一些循环结构变得简单。for循环语句是支持迭代的一种通用结构&#xff0c;是最有效&#xff0c;最灵活的循环结构。 先写第一列&#xff1a; 运行结果&#xf…

uni-appH5Android混合开发三 || uni-app调用Android原生方法的三种方式

前言&#xff1a; 关于H5的调用Android原生方法的方式有很多&#xff0c;在该片文章中我主要简单介绍三种与Android原生方法交互的方式。 uni-app跨平台框架介绍和快速入门 uni-app跨平台框架介绍和快速入门 一、H5方法调用android原生方法 H5 Android开发规范官方文档&#…

stm32开发三、GPIO

部分引脚可容忍5V&#xff0c;容忍5V的意思是:可以在这个端口输入5V的电压&#xff0c;也认为是高电平 但是对于输出而言&#xff0c;最大就只能输出3.3V&#xff0c;因为供电就只有3.3V 具体哪些端口能容忍5V&#xff0c;可以参考一下STM32的引脚定义 不带FT的&#xff0c;就只…

Java通过百度地图API获取定位-普通IP定位

项目中有一个登录邮箱提醒的功能&#xff0c;需要根据IP地址获取定位信息&#xff0c;从而更好地提示用户账号登录的所在地。为此&#xff0c;花费了一些时间来实现这个功能。 在CSDN搜索了一下&#xff0c;发现关于获取定位的文章说明都不够详细&#xff0c;于是决定自己创作一…

CAP与BASE分布式理论

一、分布式理论 1.CAP理论 CAP理论是说对于分布式数据存储&#xff0c;最多只能同时满足一致性&#xff08;C&#xff0c;Consistency&#xff09;、可用性&#xff08;A&#xff0c; Availability&#xff09;、分区容忍性&#xff08;P&#xff0c;Partition Tolerance&…

编程式导航

目录 一、问题引入 二、基本跳转 1.path路径跳转&#xff08;简易方便&#xff09; 2.name命名路由跳转&#xff08;适合path路径长的场景&#xff09; 三、路由传参 1.path路径跳转传参 &#xff08;1&#xff09;query传参 &#xff08;2&#xff09;动态路由传参 2.…

揭秘VSCode魔法工具箱:HTML5 CSS3超强插件集合 + Css Reset与Normalize.css的终极对决

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍HTML5、CSS3常用的vscode插件&#x1f34e;1 HTML 标签同步重命名– Auto…

每日一题1:从表中创建DataFrame

本文通过一道题来对创建DataFrame知识点进行拓展&#xff0c;方便以后直接调用。下面先对知识点进行介绍&#xff1a; 在Python中&#xff0c;使用pandas库创建DataFrame有多种方法&#xff0c;这里列举几种常见的创建方式&#xff1a; 一、常见创建方式 1. 从二维列表创建 …

Android之给Button上添加按压效果

一、配置stateListAnimator参数实现按压效果 1、按钮控件 <Buttonandroid:id"id/mBtnLogin"android:layout_width"match_parent"android:layout_height"48dp"android:background"drawable/shape_jfrb_login_button"android:state…

融知财经:期货和现货的区别是什么?哪个风险大?

期货和现货在交易对象等方面存在明显的区别。期货交易是一种衍生金融工具&#xff0c;主要用于价格发现、风险管理和投机&#xff0c;而现货交易则是商品和服务的实际买卖。在选择进行期货交易还是现货交易时&#xff0c;投资者需要根据自己的需求和市场情况来决定。 期货和现货…

如何访问远程MySQL数据库服务器?

访问远程MySQL数据库服务器是一项常见的任务&#xff0c;它允许我们在不同的地点通过网络连接到MySQL服务器&#xff0c;并进行数据库管理和数据处理操作。我们将重点介绍一种名为【天联】的组网技术&#xff0c;该技术提供了一系列优势&#xff0c;使远程访问MySQL数据库服务器…

javac编译web项目中的src

对于单个文件的且不引用其他类文件的java源码用javac编译大家都很熟悉即 javac hello.java, 服务器未安装idea&#xff0c;现在在服务器里面直接编译src目录 1 idea项目结构如下 2 web目录为最终部署的代码 WEB-INF下面没有 classes 目录 3 使用javac 编译src javac -encod…

Nachi那智不二越机器人维修技术合集

一、Nachi机械手维护基础知识 1. 定期检查&#xff1a;定期检查机器人的各个部件&#xff0c;如机械手伺服电机、机器人减速器、机械臂传感器等&#xff0c;确保其运行正常。 2. 清洁与润滑&#xff1a;定期清洁Nachi工业机器人表面和内部&#xff0c;并使用合适的润滑油进行润…

在线旅游网站,基于 SpringBoot+Vue+MySQL 开发的前后端分离的在线旅游网站设计实现

目录 一. 前言 二. 功能模块 2.1. 登录界面 2.2. 管理员功能模块 2.3. 用户功能模块 三. 部分代码实现 四. 源码下载 一. 前言 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff0c;旅游网站当然…