众所周知,当我们要实现一个高并发的服务,要应对大流量时代带来的服务器的压力,必须从程序的角度上完成一个近乎完美的并发编程,相信我,Go将是各位的不二之选!
下面就闲谝以下Go语言如何实现并发!
并发
针对部分刚刚入坑的小白来说,有必要阐述以下并发与并行的区别,在计算机领域,这是两个非常重要的概念。简单地说:并发是多个事情在一个物理处理器上运行,轮询执行,实现多个事情同时做的错觉;而并行,则针对那些物理处理器多的设备,实现多个事情真的是同一时间在执行!
线程
线程是一个执行空间,这个执行空间会被操作系统调度器选择运行其中的代码片段,不同的操作系统使用的线程的调度算法都不一样,不过这不是我们所关心的。
进程
进程是操作系统进行资源分配的基本单位,维护了应用程序运行时的内存地址空间,设备和文件的句柄以及线程
Goroutine
区别于其他语言,操作系统会在物理处理器上调度线程来运行,而Go语言会在逻辑处理器上调度goroutine来运行,每个逻辑处理器会分别绑定单个操作系统线程
线程,逻辑处理器,goroutine之间的关系可以使用下图描述
下面简单的创建goroutine实现并发编程
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
// 指定逻辑处理器的数量为1
runtime.GOMAXPROCS(1)
// 创建一个变量wg,等待程序执行完成
var wg sync.WaitGroup
// 此处代表主线程需要等待goroutine执行完成
wg.Add(2)
// 声明一个匿名函数,创建一个goroutine1
go func() {
defer wg.Done() // 函数执行完成时调用Done函数告诉main函数我的工作已经完成
for i := 0; i < 10; i++{
fmt.Println(i)
}
}()
// 声明一个匿名函数,创建一个goroutine2
go func() {
defer wg.Done()
for i := 'a'; i < 'a'+26; i++{
fmt.Println(i)
}
}()
// 等待两个g执行结束
wg.Wait()
}
可以看出,go语言实现并发就是这么easy!
竞争状态与锁住共享资源
接触过其他语言的童鞋都知道,一提到并发,一提到线程,总避免不了互相访问共享资源带来的各种问题,在Python里面我们讲线程是非安全的,需要加以各种机制,在Go语言里面,提出了竞争状态这个概念
何为竞争状态?当两个或多个goroutine在没有互相同步的前提下,访问某个共享资源,并试图同时去读写这个资源,就会产生竞争状态,当然这种状态是我们不想看到的
Go给我们提供了竞争检测器,如何使用呢?只需go build -race,竞争检测器就会向我们指出当前程序哪里存在潜在问题
在没有接触通道这个概念之前,其他语言总是会使用锁的机制来限制这种共享资源带来的安全问题,同样地,Go语言也提供了传统的锁机制
目前锁住共享资源存在以下几种解决策略
- 使用原子函数,原子函数会将调用互相同步,保证同一时间只有一个操作修改共享资源;
- 使用互斥锁,这个概念基本上每种语言都会去支持,简单的讲,其思想就是在代码上创建一个临界区,保证同一时间只能有一个goroutine去执行这个临界区的代码,请看代码片段
package main
import (
"sync"
)
var (
inst int
wg sync.WaitGroup
// 用来定义一段代码隔离区
mutex sync.Mutex
)
func main() {
wg.Add(2)
go addInst(1)
go addInst(2)
wg.Wait()
}
func addInst(id int) {
defer wg.Done()
for i:=0;i<10;i++{
mutex.Lock()
{
num := inst
num++
inst = value
}
mutex.Unlock()
}
}
通道
好吧,上面说的那些同步机制只是个开胃菜,对于Go语言来说,主菜是通道,这种通道机制无疑给goroutine插上了梦的翅膀,使得Go语言的并发性能扶摇九万里
当一个资源需要在goroutine之间共享时,通道的引入给各个goroutine之间开辟了一个管道,这个管道分为有缓冲以及无缓冲两种模式
下面简单声明一个通道
// 创建一个无缓冲的通道
unbuffered = make(chan int)
// 创建一个有缓冲的通道,包含10个值
buffered = make(chan int, 10)
无缓冲的通道与有缓冲的通道有何差别?相信看了以下的图解就会一目了然
无缓冲通道:
指在接收之前没有能力保存任何值,这就要求两个goroutine,作为发送方以及接收方,其动作必须一致,同步化,才可完成通道资源的发送与接收,否则会出现一方阻塞的情况,可以模拟两个人打网球的场景;
有缓冲通道:
指在未接收之前可以存储一个或者多个值的通道,并不强制要求发送方与接收方动作同步,只有在通道中没有要接收的值时,接收动作才会阻塞;在通道没有可用缓冲区存储资源时,发送动作才会阻塞,可以想象一个资源池的场景,很多资源需要被多个 goroutine去完成,可以使用有缓冲的通道实现;
通道关闭后,接收方仍可以从其中获取数据,保证数据不会丢失!