非零基础自学Golang

第13章 并发与通道

并发是指在同一段时间内,程序可以执行多个任务。

随着社会需求的发展,光靠硬件的提升是无法满足高并发的需求的,因此从程序的角度去解决并发问题就显得尤为重要。

最早支持并发编程的语言是汇编语言,但那时没有任何理论来支持这种编程模式,因此一个细微的编程错误就可能导致程序变得非常不稳定。

到现在为止,基本所有的语言都支持并发编程,而Go语言最大的特点就是从语言层面支持并发。

下面会详细讲解Go语言如何利用协程(coroutine)和通道(channel)来解决并发问题。

13.1 概述

在程序中往往有很多很耗时的工作,比如上传文件、下载文件、网络聊天。这时候,一个线程是服务不了多个用户的,会产生因为资源独占导致的等待问题,这时候就需要使用并发的手段来解决。

并发编程的含义比较广泛,包括多线程编程、多进程编程以及分布式程序。

本章所讲述的并发叫作协程(coroutine),属于多线程编程。

在计算机刚出现的那个年代并没有“并发”这个概念,因为以前的命令式程序是以串行为基础,程序会依次执行每一条指令,整个程序只有一个上下文和一个调用栈。

并发则意味着程序可以有多个上下文、多个调用栈。

【提示】

调用栈是计算机科学中存储有关正在运行的子程序的消息的栈,经常被用于存放子程序的返回地址。

在调用任何子程序时,主程序都必须暂存子程序运行完毕后应该返回到的地址。因此,如果被调用的子程序还要调用其他的子程序,其自身的返回地址就必须存入调用栈,在其自身运行完毕后再取回。

13.1.1 并行与并发

很多时候我们会把“并行”和“并发”理解成同一个概念,但实际上,它们有着清晰的区别。

我们先来看它们的定义。

  • 并行(parallelism):指在同一时刻,有多条指令在多个处理器上同时执行。
  • 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速地轮换执行,得到在宏观上有多个进程同时执行的效果,但在微观上并不是同时执行,只是把时间片分成了若干段,使得多个进程快速交替执行。

并行是真正意义上的同时执行,而并发只是从宏观的角度来看具有同时执行的效果。

如下左图所示,

在这里插入图片描述

对于任务A和B,在任何时刻,任务A、B都在执行,这就是并行。如上右图所示为并发,在任何时刻,有且仅有一个任务在执行,但由于计算机处理速度非常快,对于任务A和B,从宏观的角度来看就感觉它们是在同时执行。

13.1.2 Go并发优势

Go语言的优势在于从语言层面上支持了并发,并发编程的内存管理有时是非常复杂的,而开发者不用担心并发的底层逻辑、内存管理,因为这些在语言层面上已经解决了,开发者只需要编写好自己的业务逻辑即可。

Go语言也提供了十分强大的自动垃圾回收机制,开发者不用担心创建的量如何销毁。【牛逼!】

在Go语言里,想要编写一个并发程序是非常容易的事,它不需要额外引用其他的第三方库,只需要使用“go”关键字就可以实现。

在其他语言中,编写并发程序往往需要使用其他的并发库才能实现。【Java 高并发可是一门… 】

在Go语言里,只需要使用“go”加上函数名称就可以让这个函数变为并发函数,如下所示:

[ 动手写 13.1.1]

package main

func run(arg string) {
   // 此线程的任务
}

func main() {

   go run("this is new thread")
}

Go语言的并发基于CSP(Communication Sequential Process,通信顺序进程)模型,CSP模型是在20世纪70年代提出的用于描述两个独立的并发实体通过共享的通信管道(channel)进行通信的并发模型。

CSP中channel是一类对象,它不关注发送消息的实体,而关注发送消息时使用的通信管道。

简单来说,CSP模型提倡通过通信来共享内存,而非通过共享内存来通信。

基于CSP模型,也就意味着显式锁都是可以避免的,比如资源竞争——多个进程同时获取文件资源并需要进行修改时,首先拿到资源的进程会加上锁,等修改完之后再把锁去掉,然后提供给下一个进程来进行修改,只有这样才不会出现数据不一致。

但是Go语言不是通过锁的方式,而是通过通信的方式,通过安全的通道发送和接收数据以实现同步,这就大大简化了并发编程的编写。【厉害】