Go语言程序设计(十七)并发编程

news2024/9/26 3:24:33

一、操作系统提供的并发基础

1、进程

        进程是在并发环境下,程序的一次动态执行过程。它由进程控制块(PCB)、程序和数据三部分组成,进程在它的生命周期内可能处于执行、就绪、阻塞三种基本状态。
        在多任务操作系统中,多个进程可以并发执行,而且进程是系统资源分配的基本单位。系统中每个进程都有自已的内存映像区,且互不影响,所以管理简单,但缺点是系统开销大。所以,系统能同时创建的进程数量是有限的,不能太多。


2、线程

        由于进程的系统开销大,操作系统的设计者又提出了更小的能独立运行的单位一一线程,试图用它来提高系统内程序并发执行的程度,从而进一步提高系统的吞吐量。
        在操作系统中,线程是由进程创建的,所以它继承了进程的部分资源,且具有进程的一些基本特征。所以多个线程之间也可以并发执行,且比进程的系统开销小。但是,和进程一样,线程依然是由系统内核管理的,所以在高并发模式下,系统能创建的线程数量依然有限,效率也并不高。


3、协程

        协程本质上是一种用户态线程,不需要操作系统进行抢占式调度,而且在真正的实现中寄存于线程中。因此,协程系统开销极小,可以有效提高线程任务的并发性,避免高并发模式下线程的缺点。协程的最大优势在于其“轻量级”,可以轻松创建上百万个而不会导致系统资源衰竭,而系统最多能创建的进程、线程的数量却少得多。
        使用协程的优点是编程简单,结构清晰。但缺点是需要语言的支持,如果语言不支持,则需要用户在程序中自行实现调度。目前,原生支持协程的语言还很少。

二、Goroutine

1、Goroutine的定义

        Go语言在语言级别支持轻量级线程,叫做Goroutine。Go语言标准库提供的所有系统调用操作(包括同步I/O操作),都会让出处理机给其他Goroutine。这使得轻量级线程的切换管理不依赖于系统的进程和线程,也不依赖于CPU的核心数量。
        Goroutine是Go语言运行库的功能,不是操作系统提供的功能,Goroutine不是用线程实现的。Goroutine 就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。因为节省了频繁创建和销毁线程的开销,所以它相对于进程、线程系统开销非常小,是轻量级的。可以很轻松地创建上百万个Goroutine,但它们并不是被操作系统所调度执行。

2、Goroutine的创建

        Goroutine是Go语言中的轻量级线程实现,由Go运行时管理(Runtime)。在一个函数调用前加上关键字“go",这次调用就会在一个新的Goroutine中并发执行。当被调用的函数返回时,这个Goroutine也就自动结束了。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。
        在Go语言中,可以使用关键字“go"创建并发执行的Goroutine。基本格式如下:

go func()

例如:

func test() {
    fmt. Println("Go...")
}

func main() {
    for i :=0; i< 10; i++{
        go test()
    }
}

        在上面的例子中,在一个for循环中一共调用了10次test()函数,它们是并发执行的。可是在编译执行上述代码时,会发现显示器没有任何输出信息,要解释这种现象,首先要了解Go语言程序的执行机制。
        Go程序从初始化main package并执行main()函数开始,当main()函数返回时程序退出,并且程序并不等待其他Goroutine结束。对于上述例子,main()函数启动了10个Goroutine,然后就直接返回退出了。而被启动的执行test()函数的10个Goroutine并没有来得及执行,所以程序没有任何输出结果。
        在多进程或多进程编程时,操作系统解决上述问题的方法是,让父进程等待线程执行结束后再退出,比如使用WaitForSingleObject之类的调用,来等待所有线程执行完毕。而Go语言有自已特有的解决方式,那就是Channel(通道)。Channel可以在Goroutine之间进行通信,这样main()函数就可以知道Goroutine何时退出。通过Channel,main()函数就可以等待所有Goroutine都退出了自己再退出。
        所以,在使用Go语言设计并发程序时,通常是Goroutine和Channel配合使用,二者不可缺其一。

三、Channel

1、程序间的并发通信

(1)共享内存
        共享内存是指多个并发单位分别保存对同一个数据的引用,实现对该数据的共享。被共享的数据可以有多种形式,比如内存数据块、磁盘文件、网络数据等,在实际应用中最常见的就是内存数据块。
        多个并发单位在同时访问共享内存时,必须使用互斥锁等相关机制,以保证对共享内存的互斥访问。这就造成了在多个并发单位间使用共享内存通信时,程序结构往往比较复杂,程序逻辑结构难于控制等问题。
(2)消息机制
        Go语言是以并发编程作为语言的最核心优势,所以在处理程序并发模型时,它不再采用共享内存作为并发单位之间的通信手段,而是以消息机制作为主要通信方法。
        消息机制规定每个并发单位是自包含的、独立的个体,并且都有自己的变量,这些变量不能在不同的并发单位之间共享。每个并发单位的输入、输出只有一种,那就是消息。这有点类似于进程的概念,每个进程都不会被其他进程打扰,它只做好自己的工作就行了。不同进程间靠消息进行通信,而不会共享内存数据。

2、Channel简介

        Go语言提供的消息通信机制被称为Channel,它类似于单双向数据管道(Pipe),用户可以使用Channel在两个或多个Goroutine之间传递消息。Channel从设计上确保同一时刻只有一个Goroutine能从中接收数据,这就避免了使用互斥锁的问题。另外,Channel中数据的发送和接收都是原语操作,不会中断,只会失败。
        Channel是进程内的通信方式,因此通过Channel传递对象的过程和调用函数时的参数传递行为比较一致,当然也可以传递指针等。如果需要跨进程进行通信,一般建议使用分布式系统的方法来解决,比如使用网络套接字(Socket)或者HTTP等通信协议。

3、Channel声明和初始化

        在Go语言中,Channel是引用类型,也是类型相关的,也就是说一个Channel只能传递一种类型的值,这个类型需要在声明Channel时指定。Channel的一般声明形式如下:

var chanName chan ElementType

        从上式可以看出,Channel的声明格式和一般变量声明基本相同,只是在类型前加了个关键字“chan”。ElementType指定Channel所能传递的元素类型。
例如:

var ch chan int

        该例中,ch是一个可以传递int类型的Channel。
Channel除了可以传递基本类型的数据,还可以作为Array、Slice或Map的元素。
例如:

var chs [10]chan int

该例中,chs是一个包含10个可传递int类型数据的Channel。
还可以使用make()函数直接声明并初始化Channel,

例如:

ch : = make( char int)

该例中声明并创建了ch,并为其分配了内存。

4、数据接收和发送

        Channel的主要用途是在不同的Goroutine之间传递数据,它使用通道运算符“<-”接收或者发送数据,将一个数据发送(写入)至Channel的语法如下:

ch <- value

        向Channel写入数据通常会导致程序阻塞(Block),直到有其他Goroutine从这个Channel中读取数据。从Channel中接收(读取)数据的语法如下:

value :=<- ch

        如果Channel之前没有写入数据,那么从Channel中读取数据也会导致阻塞,直到Channel中被写入数据为止。

5、Channel的关闭和迭代器

        关闭Channel非常简单,直接使用Go语言提供的内置丽数close()即可。关闭Channel的操作语句如下:

close( chanName)

        在关闭了一个Channel之后,往往用户还需判断Channel是否被关闭,这时可以在读取Channel的时候使用多重返回值的方式,例如:

value,ok :=<- ch

        这个用法与Map中按键值获取value的过程比较类似,只需要看第二个bool返回值即可。如果返回值是false则表示Channel已被关闭,否则主函数还要继续阻塞接收或者发送。
        对Channel的读取操作还可以使用range迭代器来完成,range操作直至Channel关闭(Close)方才终止循环。另外,在Go语言中,还经常把创建Goroutine和Channel的工作放在一个匿名函数中来完成。

        只有发送端(另一端正在等待接收)才能关闭Channel,只有接收端才能获得关闭状态。Close调用不是必需的,但如果接收端使用range或者循环接收数据,就必须调用Close,否则就会导致“throw: all goroutines are asleep-deadlock !”错误。

6、单向Channel

前面例子中列举的通道既能发送数据,也能接收数据,被称为双向通道(Duplexchannel)。还可以将Channel指定为单向通道(Simplex- channel),即只能接收,或只能发送。在将一个Channel变量传递到一个函数时,可以通过将其指定为单向Channel变量,从而限制该函数对此Channel的操作,比如只能从此Channel读,或只能往该Channel写。
只能接收的Channel变量定义形式如下:

var chanName chan <- ElementType

只能发送的Channel变量定义形式如下:

var chanName <- chan ElementType

        在定义了单向Channel后,还要对其初始化。在Go语言中,Channel是引用数据类型,也是一个原生数据类型,因此不仅支持被传递,还支持类型转换。所以,单向Channel可以由一个已定义的双向(正常)Channel转换而来。
例如:

ch := make(chan int)
chRead :=<- chan int(ch)
chWrite := chan <- int(ch)

        该例中基于ch,通过类型转换初始化了两个单向Channel:chRead是一个单向读Channel,chWrite是一个单向写Channel。

7、异步Channel

        前面的举例创建的都是不带Buffer的Channel,这种做法只适用于传递单个数据的应用场合,而对需要持续传输大量数据的应用场合就不适用了。对于在Goroutine间传输大量数据的应用,可以使用异步通道(Asynchronouschannel),从而达到消息队列的效果。

        异步Channel,就是给Channel设定一个Buffer值。在Buffer未写满的情况下,不阻塞发送操作;在Buffer未读完之前,不阻塞接收操作。这里的Buffer是指被缓冲的数据对象的数量,而不是内存大小。
要创建一个带Buffer的Channel,只需要在调用make()函数时,将缓冲区的大小作为第二个参数传入即可。
例如:

ch : = make(chan int, 1024)

        该例创建了一个大小为1024 的int类型Channel,即使没有读取方,写入方也可以一直往Channel中写入数据,在缓冲区被写满之前都不会阻塞。
        从带Buffer的Channel中读取数据,可以使用与常规非缓冲Channel完全一致的方法,但一般是使用range来实现更为简洁的循环读取。

四、Select机制

        在Go语言中,Select机制主要用于解决通道通信中的多路复用问题。因为通道的接收操作往往是阻塞式的,所以Select机制还经常和超时机制配合使用,将阻塞式的通信转换为非阻塞式,以提高系统通信效率。

如果有多个Channel需要监听,就可以使用Select机制,随机处理一个可用的Channel。Select的用法和Switch语句非常类似,由“select"开始一个新的选择块,每个选择条件由“case”语句来描述。与Switch语句可以使用任何形式条件表达式相比,Select机制有比较多的限制,其中最大的一条是每个“case”语句必须是一个I/O操作。

Select 机制的基本结构如下:

select {
    case <- chan1:
        //如果chan1成功读取数据,则进行该case处理语句.
    case <- chan2:
        //如果chan2成功读取数据,则进行该case处理语句.
        .
        .
        .
    default:
        //如果上面都没有成功,则进入default处理流程.
}

        可以看出,Select不像Switch语句,后面并不带判断条件,而是直接检测case语句。每个case语句都必须是一个面向Channel的操作。

Select机制的基本过程

(1)当所有被监听Channel中都无数据时,Select会一直等到其中一个有数据为止。
(2)当多个被监听Channel中都有数据时,Select会随机选择一个case执行。
(3)当所有被监听Channel中都无数据,且default子句存在时,则default子句会被执行。
(4)如果想持续监听多个Channel,需要使用for语句协助。
 

五、超时机制

        在前面所有举例中,所有对Channel操作的错误问题都被忽略了,没有进行错误处理的程序显然是不安全的。即在向Channel写数据时发现已满,或从Channel读数据时发现为空,如果不正确处理这些问题,很可能会导致整个Goroutine死锁。在Go语言并发编程的通信过程中,所有错误处理都由超时机制来完成。
        超时机制是一种解决通信死锁的机制,通常会设置一个超时参数,通信双方如果在设定的时间内仍然没处理完任务,则该处理过程会立即被终止,并返回对应的超时信息。
        Go语言没有提供直接的超时处理机制,但可以利用Select机制解决超时问题。因为Select的特点是只要其中一个case已经完成,程序就会继续往下执行,而不会考虑其他case的执行情况。基于此特性,就可以使用Select为Channel实现超时处理功能。

六、Runtime Goroutine

1、出让时间片

        在设计并发任务时,用户可以在每个Goroutine中控制何时主动让出时间片给其他Goroutine,这可以使用Gosched() 函数来实现。Gosched()类似于C#或Python中的Yield(函数迭代器),让出当前Goroutine的执行权限,调度器会安排其他等待的任务去运行,并在下轮某个时间片再从该位置恢复执行。

2、获取CPU核心数和任务数

        有时为了将多个并发执行的Goroutine分配给不同的CPU核心去完成,用户就需要知道CPU核心的具体数目。为此,runtime包提供了NumCPU()函数可以完成这个任务。
        为了观察系统任务调度情况,还可以使用NumGoroutine()函数返回正在执行和排队的任务总数。

3、终止当前Goroutine

        如果要强行终止一个Goroutine的执行,可以调用Goexit()函数来完成。Goexit()将终止整个堆栈链,并在内层退出,但是defer语句仍然或被执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/731378.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

<数据结构>NO9.选择类排序|直接选择排序|堆排序

文章目录 选择排序1.直接选择排序优化直接选择排序 2. 堆排序 选择排序 基本思想 选组排序是从待排序数据中选出最大/最小的元素放入到序列的起始位置&#xff0c;直到待排序数据全部有序。 直接选择排序和堆排序的基本思想均符合选择排序。 1.直接选择排序 假设数据按升序…

Codeforces Round 883 (Div. 3)

A. Rudolph and Cut the Rope 只需要按照钉子距离的高度a_{i}和绳子的长度b_{i}的差值进行排序即可 代码 int n; pii a[N]; bool cmp(pii a,pii b) {return a.x-a.y<b.x-b.y; }void solve() {cin>>n;for(int i1;i<n;i)cin>>a[i].x>>a[i].y;sort(a1,…

36.RocketMQ之Broker如何实现磁盘文件高性能读写

highlight: arduino-light Broker读写磁盘文件的核心技术:mmap Broker中大量的使用mmap技术去实现CommitLog这种大磁盘文件的高性能读写优化的。 通过之前的学习&#xff0c;我们知道了一点&#xff0c;就是Broker对磁盘文件的写入主要是借助直接写入os cache来实现性能优化的&…

如何判断开发的商城系统是不是O2O平台?

纵观如今各大电商模式&#xff0c;数O2O发展最为火热&#xff0c;毫不夸张的说&#xff0c;O2O模式已经渗透到了很多行业中&#xff0c;那么哪些平台属于O2O呢?小编收集了一些资料&#xff0c;并将当下O2O平台分为以下这4种类型&#xff0c;下面小编就来带大家一起去了解了解!…

多模态系列论文--CoCa 详细解析

论文地址&#xff1a;CoCa: Contrastive Captioners are Image-Text Foundation Models 代码地址&#xff1a;CoCa CoCa 1 摘要2 网络结构3 损失函数4 实验结果5 总结 1 摘要 CoCa代表Contrastive Captioners的缩写&#xff0c;代表模型用两个目标函数训练出来的&#xff0c;一…

回溯法解决地图填色问题

目录 回溯法 最大度优先 最少可选颜色优先 向前探测 随机产生不同规模的图&#xff0c;分析算法效率与图规模的关系&#xff08;四色&#xff09; 回溯法 回溯法的基本思想是采用递归和深度优先搜索的方法&#xff0c;尝试在一组可能的解中搜索出符合要求的解&#xff0c…

Langchain的新课程;Mozilla开发的AI文档工具遭到开发者批评

&#x1f989; AI新闻 &#x1f680; Mozilla开发的AI文档工具遭到开发者批评&#xff0c;已下线 摘要&#xff1a;Mozilla开发的基于生成式AI的工具AI Help在开发者群体中遭到广泛批评。开发者认为该工具提供的信息常常错误&#xff0c;甚至认为它没有这个功能反而更好。针对…

Flutter 仿抖音、豆瓣、知乎、番茄小说的评论弹窗开发实践

最近用flutter做了一个评论弹窗的功能&#xff0c;本来以为很简单的烂大街的一个功能&#xff0c;结果却遇到了不少的问题&#xff0c;而且这些问题我觉得很有意义&#xff0c;以至于我觉得我如果分享出来可能会对其他人很有帮助。 要做一件事情可能会很容易&#xff0c;但做好…

ModaHub魔搭社区: AI模型社区ModelScope和Hugging Face行业分析报告

目录 引言&#xff1a; 一、github星数&#xff1a; 二、模型数&#xff1a; 三、数据集&#xff1a; 四、演示应用程序数&#xff1a; 五、下载数&#xff1a; 六、开发者、付费企业和公司用户数&#xff1a; 结论&#xff1a; 引言&#xff1a; AI模型开源社区在近年…

MQTT资料储备

1、官网 https://mqtt.org/ MQTT 5.0官方协议 https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html 说明&#xff1a;官网可以获取到好多资料&#xff08;比如常用软件、协议、usecase等&#xff09; 2、安装部署EMQX 当前有好多MQTT服务器&#xff0c;初步选择了EM…

MyBatis查询数据库【秘籍宝典】

0.MyBatis执行流程1.第一个MyBatis查询1.创建数据库和表1.2.添加MyBatis框架依赖【新项目】1.3.添加MyBatis框架依赖【旧项目】1.4.配置连接数据库1.5.配置MyBatis的XML路径2.MyBatis模式开发2.1 添加MyBatis的xml配置 3.增查改删&#xff08;CRUD&#xff09;5.1.增加操作5.2.…

机器学习之多元微积分

机器学习的多元微积分跟高等数学中的多元微积分有很多不同之处。 机器学习中的变量都是向量或者矩阵机器学习中的函数一般都是线性函数&#xff0c;而不是高数中的曲线和曲面、体积等函数。因此&#xff0c;机器学习中的微积分跟线性代数结合在一起。机器学习中导数的分子分母…

Chapter 4: Functions | Python for Everybody 讲义笔记_En

文章目录 Python for Everybody课程简介FunctionsFunction callsBuilt-in functionsType conversion functionsMath functionsRandom numbersAdding new functionsDefinitions and usesFlow of executionParameters and argumentsFruitful functions and void functionsWhy fun…

GEE:GEE上一些好看的可视化参数

作者&#xff1a;CSDN _养乐多_ 为了方便使用&#xff0c;总结了几种可视化参数。 文章目录 一、连续型1.11.21.31.41.5 二、离散型2.1 一、连续型 1.1 var phenoPalette [ff0000,ff8d00,fbff00,4aff00,00ffe7,01b8ff,0036ff,fb00ff]1.2 var evapotranspirationVis {min…

异地手机卡如何在当地办理宽带【纯教程篇】

有一些小伙伴常年在外漂泊会经常使用异地卡套餐&#xff0c;但是当你需要办理宽带业务的时候&#xff0c;很多时候当地的营业厅会让你再办理一张他们本地的手机卡&#xff0c;不方便不说还要多支付一张卡的月租也不划算。 其实呢运营商早就有异地办理宽带的业务&#xff0c;只不…

公司新来了个卷王,还是个00后,我们这帮老油条真干不过...

都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。这不&#xff0c;前段时间我们公司来了个00后&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 …

浅析集群、分布式、负载均衡

平时开发或者面试中进场听到集群、分布式、负载均衡等系列的名词&#xff0c;他们之间有什么联系呢&#xff0c;本文就简要的抛砖引玉一下。 集群 1.什么是集群 集群一般指的是服务器集群。集群其实就是一组相互独立的计算机&#xff0c;通过高速的网络组成一个计算机系统。…

深度学习笔记之Transformer(六)Position Embedding铺垫:Skipgram与CBOW模型

深度学习笔记之Transformer——PositionEmbedding铺垫&#xff1a;Skipgram与CBOW模型 引言回顾&#xff1a; Word2vec \text{Word2vec} Word2vec模型补充&#xff1a;关于 Word2vec \text{Word2vec} Word2vec的一些说明 引言 上一节介绍了 Word2vec \text{Word2vec} Word2vec…

【动态规划算法】第九题:64. 最小路径和

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你…

Todo-List案例版本一

初级使用e.target.value 记得安装npm i nanoid与UUID类似 快捷键ctrlH替换内容 src/components/MyHeader.vue <template><div class"todo-header"><input type"text" placeholder"请输入你的任务名称&#xff0c;按回车键确认&quo…