Go语言进阶 - 并发?并行?协程? | 青训营笔记

83 阅读4分钟

这是我参与「第五届青训营 」笔记创作活动的第2天


前言

本系列文章创作基于作者的浅薄知识和其他大佬的深刻见解,主要记录青训营学习过程中我对一些东西的个人理解,也用于参加「第五届青训营」的笔记创作活动,请多指教。

Go语言进阶

本文将着重介绍Go语言高性能编程的本质,帮助大家了解并区分并发、并行和协程的概念。

语言进阶

>> 并发?并行?协程?
最初在C++学习时接触到了线程(Thread)的概念,并尝试在大作业中实现,无奈当时个人水平不足且无人指导,没有实现便草草了事;后来在学习Java的过程中,第一次真正了解到了并发和并行的概念与区别,这也打开了我对语言进阶学习的大门。

  • 并发 (Concurrent) 在讲并发之前,我写先讲讲程序运行时最基础的情况:对于一个单线程环境,程序在同一时间只能执行一个操作,这就导致当执行其他操作的请求发出时,只能等待当前操作执行完成才能进行,通俗点来说就是排队,专业点来说就是阻塞

    受限于经费等外界无法确定因素,我们无法对硬件进行升级,只能保持单核(单线程)环境,那怎样才能提高程序运行的效率呢?假设我们有3个任务,任务1需要5分钟完成,任务2需要3分钟完成,任务3需要1分钟完成,总体来看,完成这三个任务的总时间不管怎么倒腾都是固定的(9分钟),但我们可以通过调整任务执行的顺序来减少每个任务的平均等待时间。例如执行顺序为123时,每个任务的平均等待时间为4.33分钟( (0 + 5 + 8) / 3);当执行顺序为321时,每个任务的平均等待时间就变为1.67分钟( (4 + 1 + 0) / 3 ),这样看来是不是效率就提高了呢。

    然而如此调整执行顺序是基于一个任务必须执行完毕才到下一个任务执行的原则,实际运行时CPU并不会如此进行,而是将任务“打碎”,然后将这些任务碎片交替执行,这样就给了我们一种3个任务都在同时进行假象,这种多个任务同时进行的假象即为并发

  • 并行 (Parallelism) 了解了并发的概念之后,是不是感觉单核CPU很辛苦,一个核承担了这么多。若现实条件允许,我们可以使用多核CPU。现在我们处于多线程环境,每个线程都可以独立执行一个任务,这样就能实现真正意义上的多个任务同时进行,这也正是并行

    但,我们可以无限制地开启新线程来执行一个新任务吗?假如你在运营xx支付,第一天有10个用户同时发起付款请求,你大手一挥开启10个线程来处理,轻松解决;第二天有100个用户同时发起付款请求,你十分惊喜用户量一下翻了十倍,于是又开启100个线程来处理,较为轻松解决,但隐隐感觉有些不对;第三天又翻10倍,有1000个用户同时发起付款请求,你开了1000个线程来处理,但一不小心服务器内存爆了,程序崩溃,用户纷纷付款失败,骂骂咧咧地说以后再也不用这个支付了,你欲哭无泪。

    以上这个小故事告诉我们,虽然开启多线程可以提高效率,但不能无限制地多开线程,因为每个线程会占用4M的内存;除此之外,线程间切换开销也是十分大的。

  • 协程 (Coroutine) 协程刚好可以解决上述2个问题。协程占用内存仅为KB级别,并且协程间切换的开销远小于线程间切换。协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。对于上述问题,我们只需要开启100个线程,每个线程上运行10个协程,即可完成1000个支付请求。