分析Go语言中CSP并发模型与Goroutine的基本使用

 更新时间:2021年06月16日 09:07:53   作者:TechFlow2019  
我们都知道并发是提升资源利用率最基础的手段,尤其是当今大数据时代,流量对于一家互联网企业的重要性不言而喻。串流显然是不行的,尤其是对于web后端这种流量的直接载体。并发是一定的,问题在于怎么执行并发。常见的并发方式有三种,分别是多进程、多线程和协程

一、并发实现模型

1.1、多进程

在之前的文章当中我们曾经介绍过,进程是操作系统资源分配的最小单元。所以多进程是在操作系统层面的并发模型,因为所有的进程都是有操作系统的内核管理的。所以每个进程之间是独立的,每一个进程都会有自己单独的内存空间以及上下文信息,一个进程挂了不会影响其他进程的运行。这个也是多进程最大的优点,但是它的缺点也很明显。

最大的缺点就是开销很大,创建、销毁进程的开销是最高的,远远高于创建、销毁线程。并且由于进程之间互相独立,导致进程之间通信也是一个比较棘手的问题,进程之间共享内存也非常不方便。因为这些弊端使得在大多数场景当中使用多进程都不是一个很好的做法。

1.2、多线程

多线程是目前最流行的并发场景的解决方案,由于线程更加轻量级,创建和销毁的成本都很低。并且线程之间通信以及共享内存非常方便,和多进程相比开销要小得多。

但是多线程也有缺点,一个缺点也是开销。虽然线程的开销要比进程小得多,但是如果创建和销毁频繁的话仍然是不小的负担。针对这个问题诞生了线程池这种设计。创建一大批线程放入线程池当中,需要用的时候拿出来使用,用完了再放回,回收和领用代替了创建和销毁两个操作,大大提升了性能。另外一个问题是资源的共享,由于线程之间资源共享更加频繁,所以在一些场景当中我们需要加上锁等设计,避免并发带来的数据紊乱。以及需要避免死锁等问题。

1.3、协程

也叫做轻量级线程,本质上仍然是线程。相比于多线程和多进程来说,协程要小众得多,相信很多同学可能都没有听说过。和多线程最大的区别在于,协程的调度不是基于操作系统的而是基于程序的。

也就是说协程更像是程序里的函数,但是在执行的过程当中可以随时挂起、随时继续。

我们举个例子,比如这里有两个函数:

def A():
    print '1'
    print '2'
    print '3'

def B():
    print 'x'
    print 'y'
    print 'z'

如果我们在一个线程内执行A和B这两个函数,要么先执行A再执行B要么先执行B再执行A。输出的结果是确定的,但如果我们用写成来执行A和B,有可能A函数执行了一半刚输出了一条语句的时候就转而去执行B,B输出了一条又再回到A继续执行。不管执行的过程当中发生了几次中断和继续,在操作系统当中执行的线程都没有发生变化。也就是说这是程序级的调度。

那么和多线程相比,我们创建、销毁线程的开销就完全没有了,整个过程变得非常灵活。但是缺点是由于是程序级别的调度,所以需要编程语言自身的支持,如果语言本身不支持,就很难使用了。目前原生就支持协程的语言并不多,显然golang就是其中一个。

二、共享内存与CSP

我们常见的多线程模型一般是通过共享内存实现的,但是共享内存就会有很多问题。比如资源抢占的问题、一致性问题等等。为了解决这些问题,我们需要引入多线程锁、原子操作等等限制来保证程序执行结果的正确性。

除了共享内存模型之外,还有一个经典模型就是CSP模型。CSP模型其实并不新,发表已经好几十年了。CSP的英文全称是Communicating Sequential Processes,翻译过来的意思是通信顺序进程。CSP描述了并发系统中的互动模式,是一种面向并发的语言的源头。

Golang只使用了CSP当中关于Process/Channel的部分。简单来说Process映射Goroutine,Channel映射Channel。Goroutine即Golang当中的协程,Goroutine之间没有任何耦合,可以完全并发执行。Channel用于给Goroutine传递消息,保持数据同步。虽然Goroutine之间没有耦合,但是它们与Channel依然存在耦合。

整个Goroutine和Channel的结构有些类似于生产消费者模式,多个线程之间通过队列共享数据,从而保持线程之间独立。这里不过多深入,我们大概有一个印象即可。

三、Goroutine

Goroutine即golang当中的协程,这也是golang这门语言的核心精髓所在。正是因为Goroutine,所以golang才叫做golang,所以人们才选择golang。

相比于Java、Python等多线程的复杂的使用体验而言,golang当中的Goroutine的使用非常简单,简单到爆表。只需要一个关键字就够了,那就是go。所以你们应该明白为什么golang叫做Go语言不叫别的名字了吧?

比如我们有一个函数:

func Add(x, y int) int{
    z := x + y
    fmt.Println(z)
}

我们希望启动一个goroutine去执行它, 应该怎么办?很简单,只需要一行代码:

go Add(3, 4)

我们还可以用go关键字来使用goroutine来执行一个匿名函数:

go func(x, y int) {
    fmt.Println(x + y)
}(3, 4)

需要注意的是,当我们使用go关键字的时候,是不能获取返回值的。也就是说z := go Add(3, 4)是违法的。乍看起来似乎不合理,但是道理其实是很简单的。如果我们希望一个变量承接一个函数的返回值,说明这里的逻辑是串行的,那么我们使用goroutine的意义是什么?所以这里看似不合理,其实是设计者下了心思的。

以上就是分析Go语言中CSP并发模型与Goroutine的基本使用的详细内容,更多关于Go CSP并发模型 Goroutine的资料请关注脚本之家其它相关文章!

相关文章

  • 一文带你了解Go语言中的匿名函数

    一文带你了解Go语言中的匿名函数

    无论是在Go语言还是其他编程语言中,匿名函数都扮演着重要的角色,本文将详细介绍Go语言中匿名函数的概念和使用方法,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-06-06
  • Go语言实现lru淘汰策略和超时过期

    Go语言实现lru淘汰策略和超时过期

    缓存的大小是有限的,当添加数据发现剩余缓存不够时,需要淘汰缓存中的部分数据,本文主要介绍了Go语言实现lru淘汰策略和超时过期,感兴趣的可以了解一下
    2024-02-02
  • golang之数组切片的具体用法

    golang之数组切片的具体用法

    本文主要介绍了golang之数组切片的具体用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • Golang语言的跨平台UI工具包fyne使用详解

    Golang语言的跨平台UI工具包fyne使用详解

    这篇文章主要为大家介绍了Golang语言的跨平台UI工具包fyne使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12
  • Golang中反射的常见用法分享

    Golang中反射的常见用法分享

    本篇文章主要为大家详细介绍一些Go语言中常见的反射用法,涵盖了常见的数据类型的反射操作。文中的示例代码讲解详细,感兴趣的可以了解一下
    2023-01-01
  • go语言 nil使用避坑指南

    go语言 nil使用避坑指南

    这篇文章主要为大家介绍了go语言 nil使用避坑指南详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • 基于Go语言实现插入排序算法及优化

    基于Go语言实现插入排序算法及优化

    插入排序是一种简单的排序算法。这篇文章将利用Go语言实现冒泡排序算法,文中的示例代码讲解详细,对学习Go语言有一定的帮助,需要的可以参考一下
    2022-12-12
  • golang fmt占位符的使用详解

    golang fmt占位符的使用详解

    这篇文章主要介绍了golang fmt占位符的使用详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Golang使用gorm实现分页功能的示例代码

    Golang使用gorm实现分页功能的示例代码

    在提供列表接口时一般要用到分页,对于存储在某些数据库中的数据进行分页起来非常的方便,下文给出一个通过gorm进行分页并通过http返回数据的例子,感兴趣的小伙帮跟着小编一起来看看吧
    2024-10-10
  • Go依赖注入DI工具wire使用详解(golang常用库包)

    Go依赖注入DI工具wire使用详解(golang常用库包)

    依赖注入是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入,本文结合示例代码给大家介绍Go依赖注入DI工具wire使用,感兴趣的朋友一起看看吧
    2022-04-04

最新评论