Go中的并发性介绍

并发是一个很酷的话题,一旦你掌握了它,就会成为一笔巨大的财富。说实话,我一开始很害怕写这篇文章,因为我自己直到最近才对并发性不太适应。我已经掌握了基础知识,所以我想帮助其他初学者学习Go的并发性。这是众多并发性教程中的第一篇,请继续关注更多的教程。

什么是并发性,为什么它很重要?

并发是指在同一时间运行多个事物的能力。你的电脑有一个CPU。一个CPU有几个线程。每个线程通常一次运行一个程序。当我们通常写代码时,这些代码是按顺序运行的,也就是说,每项工作都是背对背运行的。在并发代码中,这些工作是由线程同时运行的。
一个很好的比喻是对一个家庭厨师的比喻。我还记得我第一次尝试煮意大利面的时候。我按照菜谱一步步地做。我切了蔬菜,做了酱汁,然后煮了意大利面条,再把两者混合起来。在这里,每一步都是按顺序进行的,所以下一项工作必须等到当前工作完成后才能进行。
快进到现在,我在烹饪意大利面条方面变得更有经验。我现在先开始做意大利面,然后在这期间进行酱汁的制作。烹饪时间几乎减少到一半,因为烹饪意大利面条和酱汁是同时进行的。

并发性与平行性

并发性与并行性有些不同。并行性与并发性类似,即同时发生多项工作。然而,在并行性中,多个线程分别在进行不同的工作,而在并发性中,一个线程在不同的工作之间游走。
因此,并发性和并行性是两个不同的概念。一个程序既可以并发地运行,也可以并行地运行。你的代码可以按顺序写,也可以按并发写。该代码可以在单核机器或多核机器上运行。把并发性看作是你的代码的一个特征,而把并行性看作是执行的一个特征。

Goroutines, the worker Mortys

Go使编写并发代码变得非常简单。每个并发的工作都由一个goroutine来表示。你可以通过在函数调用前使用go关键字来启动一个goroutine。看过《瑞克和莫蒂》吗?想象一下,你的主函数是一个Rick,他把任务委托给goroutine Mortys。
让我们从一个连续的代码开始。

package mainimport ("fmt""time"
)func main() {simple()
}func simple() {fmt.Println(time.Now(), "0")time.Sleep(time.Second)fmt.Println(time.Now(), "1")time.Sleep(time.Second)fmt.Println(time.Now(), "2")time.Sleep(time.Second)fmt.Println("done")
}
2022-08-14 16:22:46.782569233 +0900 KST m=+0.000033220 0
2022-08-14 16:22:47.782728963 +0900 KST m=+1.000193014 1
2022-08-14 16:22:48.782996361 +0900 KST m=+2.000460404 2
done

上面的代码打印出当前时间和一个字符串。每条打印语句的运行时间为一秒。总的来说,这段代码大约需要三秒钟的时间来完成。

现在让我们把它与一个并发的代码进行比较。

func main() {simpleConc()
}func simpleConc() {for i := 0; i < 3; i++ {go func(index int) {fmt.Println(time.Now(), index)}(i)}time.Sleep(time.Second)fmt.Println("done")
}
2022-08-14 16:25:14.379416226 +0900 KST m=+0.000049175 2
2022-08-14 16:25:14.379446063 +0900 KST m=+0.000079012 0
2022-08-14 16:25:14.379450313 +0900 KST m=+0.000083272 1
done

上面的代码启动了三个goroutines,分别打印当前时间和i。这段代码花了大约一秒钟完成。这比顺序版本快了三倍左右。

"等一下,"我听到你问。"为什么要等整整一秒?难道我们不能删除这一行以使程序尽可能快地运行吗?"好问题!让我们看看会发生什么。

func main() {simpleConcFail()
}func simpleConcFail() {for i := 0; i < 3; i++ {go func(index int) {fmt.Println(time.Now(), index)}(i)}fmt.Println("done")
}
done

嗯…。程序确实在没有任何慌乱的情况下退出了,但我们缺少来自goroutines的输出。为什么它们被跳过?
这是因为在默认情况下,Go并不等待goroutine的完成。你知道main也是在goroutine里面运行的吗?主程序通过调用simpleConcFail来启动工作程序,但它在工作程序完成工作之前就退出了。

让我们回到烹饪的比喻上。想象一下,你有三个厨师,他们分别负责烹饪酱料、意大利面和肉丸。现在,想象一下,如果戈登-拉姆齐命令厨师们做一盘意大利面条和肉丸子。这三位厨师将努力工作,烹制酱汁、意大利面条和肉丸。但是,在厨师们还没有完成的时候,戈登就按了铃,命令服务员上菜。很明显,食物还没有准备好,顾客只能得到一个空盘子。

这就是为什么我们在退出节目前等待一秒钟。我们并不总是确定每项工作都会在一秒钟内完成。有一个更好的方法来等待工作的完成,但我们首先需要学习另一个概念。

总结一下,我们学到了这些东西:

  • 工作被委托给goroutines。
  • 使用并发性可以提高你的性能。
  • 主goroutine默认不等待工作goroutine完成。
  • 我们需要一种方法来等待每个goroutine完成。

Channels, the green portal

goroutines之间是如何交流的?当然是通过通道。通道的作用类似于门户。你可以通过通道发送和接收数据。下面是你如何在Go中制作一个通道。

ch := make(chan int)

每个通道都是强类型的,并且只允许该类型的数据通过。让我们看看我们如何使用这个。

func main() {unbufferedCh()
}func unbufferedCh() {ch := make(chan int)go func() {ch <- 1}()res := <-chfmt.Println(res)
}
1

很简单,对吗?我们做了一个名为ch的通道。我们有一个goroutine,向ch发送1,我们接收该数据并将其保存到res。

你问,为什么我们在这里需要一个goroutine?因为不这样做会导致死锁。

func main() {unbufferedChFail()
}func unbufferedChFail() {ch := make(chan int)ch <- 1res := <-chfmt.Println(res)
}
fatal error: all goroutines are asleep - deadlock!

们碰到了一个新词。什么是死锁?死锁就是你的程序被卡住了。为什么上面的代码会卡在死锁中?

为了理解这一点,我们需要知道通道的一个重要特性。我们创建了一个无缓冲的通道,这意味着在某一特定时间内没有任何东西可以被存储在其中。这意味着发送方和接收方都必须同时准备好,才能在通道上传输数据。

在失败的例子中,发送和接收的动作依次发生。我们发送1到ch,但在那个时候没有人接收数据。接收发生在稍后的一行,这意味着在接收行运行之前,1不能被发送。可悲的是,1不能先被发送,因为ch是没有缓冲的,没有空间来容纳任何数据。

在这个工作例子中,发送和接收的动作同时发生。主函数启动了goroutine,并试图从ch中接收,此时goroutine正在向ch发送1。

另一种从通道接收而不发生死锁的方法是先关闭通道。

func main() {unbufferedCh()
}func unbufferedCh() {ch2 := make(chan int)close(ch2)res2 := <-ch2fmt.Println(res2)
}
0

关闭通道意味着不能再向它发送数据。我们仍然可以从该通道中接收它。对于未缓冲的通道,从一个关闭的通道接收将返回一个通道类型的零值。
总结一下,我们学到了这些东西:

  • 通道是goroutines之间相互交流的方式。
  • 你可以通过通道发送和接收数据。
  • 通道是强类型的。
  • 没有缓冲的通道没有空间来存储数据,所以发送和接收必须同时进行。否则,你的代码就会陷入死锁。
  • 一个封闭的通道将不接受任何数据。
  • 从一个封闭的非缓冲通道接收数据将返回一个零值。

如果通道能保持数据一段时间,那不是很好吗?这里就是缓冲通道发挥作用的地方。

Buffered channels, the portal that is somehow cylindrical?

缓冲通道是带有缓冲器的通道。数据可以存储在其中,所以发送和接收不需要同时进行。

func main() {bufferedCh()
}func bufferedCh() {ch := make(chan int, 1)ch <- 1res := <-chfmt.Println(res)
}
1

在这里,1被储存在ch里面,直到我们收到它。

很明显,我们不能向一个满了缓冲区的通道发送更多的信息。你需要在缓冲区内有空间才能发送更多。

func main() {bufferedChFail()
}func bufferedChFail() {ch := make(chan int, 1)ch <- 1ch <- 2res := <-chfmt.Println(res)
}
fatal error: all goroutines are asleep - deadlock!

你也不能从一个空的缓冲通道接收。

func main() {bufferedChFail2()
}func bufferedChFail2() {ch := make(chan int, 1)ch <- 1res := <-chres2 := <-chfmt.Println(res, res2)
}
fatal error: all goroutines are asleep - deadlock!

如果一个通道已满,发送操作将等待,直到有可用的空间。这在这段代码中得到了证明。

func main() {bufferedCh2()
}func bufferedCh2() {ch := make(chan int, 1)ch <- 1go func() {ch <- 2}()res := <-chfmt.Println(res)
}
1

我们接收一次是为了取出1,这样goroutine就可以发送2到通道。我们没有从ch接收两次,所以只接收1。

我们也可以从封闭的缓冲通道接收。在这种情况下,我们可以在封闭的通道上设置范围来迭代里面的剩余项目。

func main() {bufferedChRange()
}func bufferedChRange() {ch := make(chan int, 3)ch <- 1ch <- 2ch <- 3close(ch)for res := range ch {fmt.Println(res)}// you could also do this// fmt.Println(<-ch)// fmt.Println(<-ch)// fmt.Println(<-ch)
}
1
2
3

在一个开放的通道上测距将永远不会停止。这意味着在某些时候,通道将是空的,测距循环将试图从一个空的通道接收,从而导致死锁。
总结一下:

  • 缓冲通道是有空间容纳项目的通道。
  • 发送和接收不一定要同时进行,与非缓冲通道不同。
  • 向一个满的通道发送和从一个空的通道接收将导致一个死锁。
  • 你可以在一个封闭的通道上进行迭代,以接收缓冲区内的剩余值。

等待戈多…我的意思是,goroutines来完成,使用通道

通道可以用来同步goroutines。还记得我告诉过你,在通过无缓冲通道传输数据之前,发送方和接收方必须都准备好了吗?这意味着接收方将等待,直到发送方准备好。我们可以说,接收是阻断的,意思是接收方将阻断其他代码的运行,直到它收到东西。让我们用这个巧妙的技巧来同步我们的goroutines。

func main() {basicSyncing()
}func basicSyncing() {done := make(chan struct{})go func() {for i := 0; i < 5; i++ {fmt.Printf("%s worker %d start\n", fmt.Sprint(time.Now()), i)time.Sleep(time.Duration(rand.Intn(5)) * time.Second)}close(done)}()<-donefmt.Println("exiting...")
}

我们做了一个done通道,负责阻断代码,直到goroutine完成。done可以是任何类型,但struct{}经常被用于这些类型的通道。它的目的不是为了传输结构,所以它的类型并不重要。
一旦工作完成,worker goroutine 将关闭 done。此时,我们可以从 done 中接收,它将是一个空结构。接收动作解除了代码的阻塞,使其可以退出。
这就是我们使用通道等待goroutine完成的方式。

总结

并发可能看起来是一个令人生畏的话题。我当然认为是这样的。然而,在了解了基础知识之后,我认为实现起来真的很美。希望你们能从这个教程中有所收获我们仅仅是触及了表面,Go为我们提供的东西还有很多。下一次我们将在更多的并发性教程中见面。再见!

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

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

相关文章

Java SWT Composite 绘画

Java SWT Composite 绘画 1 Java SWT2 Java 图形框架 AWT、Swing、SWT、JavaFX2.1 Java AWT (Abstract Window Toolkit)2.2 Java Swing2.3 Java SWT (Standard Widget Toolkit)2.4 Java JavaFX 3 比较和总结 1 Java SWT Java SWT&#xff08;Standard Widget Toolkit&#xff…

C++从零开始的打怪升级之路(day30)

这是关于一个普通双非本科大一学生的C的学习记录贴 在此前&#xff0c;我学了一点点C语言还有简单的数据结构&#xff0c;如果有小伙伴想和我一起学习的&#xff0c;可以私信我交流分享学习资料 那么开启正题 今天分享的是关于list的基本函数使用 list是一个双向带头循环的…

02 使用jdk运行第一个java程序:HelloWorld

使用jdk运行第一个java程序 1 HelloWorld小案例1.1 编写流程1.2 错误示例 首先在CMD命令行里面&#xff0c;使用javac xxxx.java&#xff0c; 进行编译&#xff0c;其中会有报错&#xff1b; 然后生成xxxx.class 文件&#xff0c;然后使用java xxxx.class 进行运行。 1 HelloWo…

瑞_23种设计模式_抽象工厂模式

文章目录 1 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;1.1 概念1.2 介绍1.3 小结1.4 结构 2 案例一2.1 案例需求2.2 代码实现 3 案例二3.1 需求3.2 实现 4 总结4.1 抽象工厂模式优缺点4.2 抽象工厂模式使用场景4.3 抽象工厂模式VS工厂方法模式4.4 抽象工厂…

node.js基础--01

Author nodes&#xff1a;&#xff08;题记&#xff09; node.js is an open-source&#xff0c;cross-platform JAVAScript runtime environment。 node.js是一个开源&#xff0c;跨平台的js运行环境 common commands&#xff08;常用指令&#xff09; 1、C: enter hard …

javaScript的序列化与反序列化

render函数的基本实现 javaScript的序列化与反序列化 一&#xff0c;js中的序列化二&#xff0c;序列化三&#xff0c;反序列化四&#xff0c;总结 一&#xff0c;js中的序列化 js中序列化就是对象转换成json格式的字符串&#xff0c;使用JSON对象的stringify方法&#xff0c;…

新书速览|Python数据科学应用从入门到精通

系统教授数据科学与Python实战&#xff0c;涵盖线性回归、逻辑回归、决策树、随机森林、神经网 本书内容 随着数据存储、数据处理等大数据技术的快速发展&#xff0c;数据科学在各行各业得到广泛的应用。数据清洗、特征工程、数据可视化、数据挖掘与建模等已成为高校师生和职场…

Socket.D v2.3.9 发布(增加 node.js server 适配)

Socket.D 是基于"事件"和"语义消息""流"的网络应用层传输协议。有用户说&#xff0c;“Socket.D 之于 Socket&#xff0c;尤如 Vue 之于 Js、Mvc 之于 Http”。支持 tcp, udp, ws, kcp 传输。协议特点可参考《官网介绍》。 pyton 已开发完成&a…

STL——空间配置器

空间配置器是STL六大组件之一&#xff0c;它和其他五个组件相互配合&#xff0c;起着很关键的作用。 容器&#xff1a;各种数据结构、如vector、list、stack、deque、queue、set、map、unordered_map等等算法&#xff1a;各种算法&#xff0c;如sort、serach、copy、erase 提供…

C#用正则表达式验证格式:电话号码、密码、邮编、手机号码、身份证、指定的小数点后位数、有效月、有效日

正则表达式在程序设计中有着重要的位置&#xff0c;经常被用于处理字符串信息。 用Regex类的IsMatch方法&#xff0c;使用正则表达式可以验证电话号码是否合法。 一、涉及到的知识点 Regex类的IsMatch方法用于指示正则表达式使用pattern参数中指定的正则表达式是否在输入字符串…

计算机网络_1.6.2 计算机网络体系结构分层的必要性

1.6.2 计算机网络体系结构分层的必要性 一、五层原理体系结构每层各自主要解决什么问题1、物理层2、数据链路层3、网络层4、运输层5、应用层 二、总结三、练习 笔记来源&#xff1a; B站 《深入浅出计算机网络》课程 本节主要介绍实现计算机网络需要解决哪些问题&#xff1f;以…

这种学习单片机的顺序是否合理?

这种学习单片机的顺序是否合理&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「单片机的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01…

vue3 markdown编辑器推荐(maven-editor vditor tiptap )

最近项目需要用到markdown编辑器&#xff0c;使用了三种 maven-editor (http://www.mavoneditor.com/?spma2c6h.12873639.article-detail.9.aaad62affAKmTV)vditor (https://b3log.org/vditor/demo/index.html?utm_sourceld246.com)tiptap (https://github.com/ueberdosis/t…

随着网络的快速发展,网络安全问题也日益凸显,遇到攻击该如何处理,如何抉择合适的防护方案

DexunCloud 经过研究发现当今世界&#xff0c;随着网络的快速发展&#xff0c;网络安全问题也日益凸显。其中&#xff0c;DDoS&#xff08;分布式拒绝服务&#xff09;攻击被认为是网络安全领域里最为严重的威胁之一。毫无疑问&#xff0c;DDoS攻击不仅可以导致网络服务中断&am…

支付宝直连商户处理支付交易投诉管理,支持多商户

大家好&#xff0c;我是小悟 1、问题背景 玩过支付宝生态的&#xff0c;或许就有这种感受&#xff0c;如果收到投诉单&#xff0c;不会通知到手机端&#xff0c;只会在支付宝商家后台-账号中心-安全中心-消费者投诉-支付交易投诉那里显示。那你能一直盯着电脑看吗&#xff1f;…

假期作业 2.2

第一章 命名空间 一&#xff0e;选择题 1、编写C程序一般需经过的几个步骤依次是&#xff08; B &#xff09; A. 编辑、调试、编译、连接 B. 编辑、编译、连接、运行 C. 编译、调试、编辑、连接 D. 编译、编辑、连接、运行 2、所谓数据封装就是将一组数据和与这组数…

Flink CEP(基本概念)

Flink CEP 在Flink的学习过程中&#xff0c;我们已经掌握了从基本原理和核心层的DataStream API到底层的处理函数&#xff0c;再到应用层的Table API和SQL的各种手段&#xff0c;可以应对实际应用开发的各种需求。然而&#xff0c;在实际应用中&#xff0c;还有一类更为复…

【Linux多线程编程】互斥锁及其使用

1、互斥锁 用于解决竞争问题的一种机制。 什么是竞争&#xff0c;竞争就是多个实体同时获取一个资源&#xff0c;例如多个线程写一个全局变量。 2、Linux如何使用互斥锁 以pthread为例&#xff0c;锁的创建和使用如下&#xff1a; /* 创建锁 */ pthread_mutex_t lock PTHR…

Python实操代码(有删除重复文件,有正则提取一列)很有用的

1&#xff0c;判断所有重复的文件并执行删除 第一步&#xff1a;遍历出文件名 import os for 文件夹路径,子文件夹列表,文件列表 in os.walk(rC:\Users\17547\Desktop\SQL导入第二个文件夹):for 文件名 in os.scandir(文件夹路径):print(文件名) 第二步&#xff1a;添加了if…

AI Partition(银灿U盘分区工具)V2.0.0.3

AI Partition(银灿U盘分区工具)V2.0.0.3.zip 复制链接下载吧 https://url20.ctfile.com/f/36743220-1017367709-67f1b9?p2024 (访问密码: 2024) 支持IS903B IS902E IS916 AI Partition(银灿U盘分区量产工具) 这个是银灿官方发布的最新版U盘分区工具&#xff0c;版本号V2.0.0…