作者|李猫,单位:中移物联网有限公司实验室指南让我们回到30年前我们可以触摸的计算机:黑黑黑黑屏幕显示白色,正方形光标在文本末尾闪烁。除了专门用于对外服务的计算机外,当时的普通用户基本上都是以串行执行指令为主,一次只运行一个应用程序。随着芯片制造工艺和制造能力的提高以及图形化操作系统在全球的普及,我们平时可以在网上听歌、玩游戏、下载最新的电视剧。不仅如此,操作系统和应用程序的开发者还在想方设法压榨计算机硬件的性能,让计算机更加流畅,计算机用户可以同时处理更多的事情。本文将为您带来应用并发相关的知识以及基于Golang的A编程语言应用并发的相关编码基础。Part01并发硬件基础1.1内存是并发编程的基础硬件知识储备。首先要说的是内存。对于内存芯片,网上喜欢形容为内存颗粒,是MOS管的集合体。以半导体的名义,许多MOS管组成一个半导体组(模组),许多模组组成一个管芯。这个模具是一个内存颗粒。当然,由许多die组成的更高层次称为wafer(晶圆)。简单来说,8个MOS管组成的电路可以表示一个字节,比如ASCII'A',我们用65来表示,即01000001,那么8个MOS管用低-高-低-低-low-low-low-high电位可以表示字符A。在读写内存时,通常以8个字为一组进行操作。我们现在常用的CPU是64位的,一次可以处理64/8=8字节的数据。1.2公交车公交车的概念和我们高速公路的概念类似,就像京沪高速的存在不只是用于北京和上海之间的通勤,只要目的地是那个地理区域,车辆就可以进入京沪高速,从而提高车速,节省时间。总线是计算机各功能部件之间传递信息的公共通信干线。按照分类,它也是地址总线、数据总线和控制总线。它们用于传输数据地址、输出和控制信号。它在计算机中用于传输信息。公共频道。一个CPU要对内存中的数据进行操作,也是通过总线进行操作。一般来说,内存的读写操作不可能在一个CPU指令周期内完成。在此期间,如果多个程序同时对一个内存地址进行操作,就会出现各种意想不到的读写操作。1.3CPU在单核CPU时期,硬件一次只能处理一件事。在多任务处理的情况下,不同的任务抢占CPU以按需执行其代码。这就涉及到CPU的调度工作。通常,操作系统已经为我们做了很多。如果将编程语言实现的并发操作交给了操作系统,那么就不用太在意调度了。如果你像Golang一样有自己的协程调度器,你仍然需要了解更多。独特的调度方法。多核处理器的基本原理大同小异,对硬件的理解也完全可以参考单核。CPU使用地址总线找到内存地址,比如0x00004567。64位CPU可以操作的最大地址长度是264,32位操作系统是232。那么为什么32位CPU最大只支持4GB内存呢?算一下232是多少(友情提示1GB=1024MB=1024*1024KB=1024*1024*1024B)。Part02并发软件基础2.1多进程模型多进程模型是操作系统层面最基本的并发模型。理解起来也比较简单。比如我们想听音乐就打开音乐播放器,想玩游戏就打开。游戏程序、音乐播放器和游戏程序都是进程。我们可以在电脑里做一个专门的进程负责播放声音,让一个专门的进程负责网络连接,让一个专门的进程显示游戏画面,让各个进程做自己专注的事情,互不影响。这样做的缺点是系统开销最大,所有进程都由操作系统管理。2.2多线程模型与多进程模型相同。从操作系统的角度来看,多线程模型在系统层面也属于并发模式。到目前为止,也是程序员使用最多的,就像我们的音乐播放器一样。Music,在播放音乐的同时,会搜索当前歌曲的歌词并通过网络下载到电脑中,而搜索歌词下载的功能是通过音乐播放器进程生成一个歌词处理线程进行处理。线程模型的理解可以和进程模型的理解一样。每个线程也可以专注于做自己的事情,互不影响。这种模型的优点是系统开销比多进程模型小,但是线程太多也会影响运行。系统受到影响。2.3异步IO模型该模型的诞生源于多进程、多线程带来的系统资源快速耗尽的危机。顾名思义,异步IO并不是按部就班地做事情。当做一些耗时的事情时,应用进程/线程不会等待,而是直接执行后面的步骤,然后通知进程/线程,直到耗时的事情做完。这种模式的优点是可以开辟少量的线程做更多的事情,但是缺点也很明显。由于整个应用程序的执行过程是分散的,程序员需要花费更多的精力来处理这种分散的执行状态。2.4协程模型协程本质上是一个由进程自己管理的线程。这个线程并没有交给操作系统管理,而是实际存放在操作系统的线程中。系统开销极小,也避免了异步IO分散的缺点。目前的缺点是支持这种模式的编程语言非常少。有一些比较早被大众使用的编程语言,由于各自的历史原因,并没有大规模适配这种模式。与之匹配的,有一个比较新的编程语言——Golang对这种模型的支持还不错。下面我们通过几个Golang的示例代码来看看并发编程的一些具体操作。Part03几个代码示例示例1//变量A的非并发计算从0开始累加100次,最后输出结果示例2//变量A从0开始累加100次,每次加法由并发执行一个单独的协程运行,最终输出结果exampleExample2中会输出什么:大多数情况下是100。按照正常理解,例2不应该是1-100之间的任意数字吗?难道Go的协程也自动处理了变量抢占等一系列问题,让我们可以愉快的编码?其实先把例2中的100改成10000再看结果~再看看例3和例4:例3//非并发模式输出变量i从0-10000每次加1。例4//多个协程输出变量i,从0-10000,每次加1。示例3是一个规范良好的单协程模型,输出不会有任何意外。至于例4,大家猜是按1,2,3...9999顺序还是其他顺序输出?如果我们进行实验,我们很容易得到结果。多协程模型中的东西不是顺序的,对变量的操作也不是原子的,这和多线程模型差不多。在某些场景下,为了保证应用程序的有序执行,我们通常会使用锁来进行处理,比如示例5。例5//多协程锁处理使其有序:搬砖的例子假设左边有三堆零散的砖,我们需要将它们从左向右移动,并整齐地堆放。这种工作的并发模型是什么?一种更可执行的实现方式:每堆砖分配固定数量的人。堆砖时,为保证堆放的整齐,采用排队的方法,按顺序一块一块堆放。砖匠拿砖,搬完砖后,排在右边堆砖。左边有人送砖,右边有人堆砖。几个搬砖工只负责搬砖。这也是并发编程模型中常见的编程思想,以后也会遇到类似的开发场景。这些例子也可以应用。一个实际案例我们以一个实际案例结束。本案例为某云平台设备信息导出代码,包含多协程拉取数据示例。整体流程如下:参数初始化定义一个接收协程,结束信息通道开启N个协程,协程调用API获取信息,每个协程根据分页参数获取(总数/N)信息,每次page=X+N,每次获取到的信息作为final放入excelbuffer。主进程循环获取每个协程结束的信息,直到所有协程任务完成。将excel缓冲区数据写入excel文件。案例链接如下(cm-heclouds是物联网公司平台部开源代码专用存放账号):https://github.com/cm-heclouds/onenet_device_export/releases/tag/2018-latest当然,这种情况下还是存在比较大的并发问题,需要改进空间,下面结合搬砖的例子看看如何改进。
