六、Golang的并发

news2024/12/28 18:55:09

Go语言的并发指的是能让某个函数独立于其他函数运行的能力。当一个函数创建为goroutine时,Go会将其视为一个独立的工作单元。这个单元会被调度到可用的逻辑处理器上执行。
Go语言运行时的调度器是一个复杂的软件,能管理被创建的所有goroutine并为其分配执行时间。这个调度器在操作系统之上,将操作系统的线程与语言运行时的逻辑处理器绑定,并在逻辑处理器上运行goroutine。调度器在任何给定的时间,都会全面控制哪个goroutine要在哪个逻辑处理器上运行。
Go语言的并发同步模型来自一个叫做通信顺序进程 (CSP)的范型。CSP是一种消息传递类型,通过在goroutine之间传递数据来传递消息,而不是对数据进行加锁来实现同步访问。用于在goroutine之间同步和传递数据的关键数据类型叫做通道

一、并发与并行
操作系统会在物理处理器上调度线程来运行,而Go语言的运行时会在逻辑处理器上调度goroutine来运行。每个逻辑处理器都分别绑定到单个操作系统线程。这些逻辑处理器会用于执行所有被创建的goroutine。即便只有一个逻辑处理器,Go也可以以神奇的效率和性能,并发调度无数个goroutine。
下图展示了Go调度器如何管理goroutine。
在这里插入图片描述
可以看到操作系统线程、逻辑处理器和本地运行队列之间的关系。如果创建一个goroutine并准备运行,这个goroutine就会被放到调度器的全局运行队列中。之后,调度器就将这些队列中的goroutine分配给一个逻辑处理器,并放到这个逻辑处理器对应的本地运行队列中。本地运行队列中的goroutine会一直等待直到自己被分配的逻辑处理器执行。
有时,正在运行的goroutine需要执行一个阻塞的系统调用,如打开一个文件。当这类调用发生时,线程和goroutine会从逻辑处理器上分离,该线程会继续阻塞,等待系统调用的返回。与此同时,这个逻辑处理器就失去了用来运行的线程。所以,调度器会创建一个新线程,并将其绑定到该逻辑处理器上。之后,调度器会从本地运行队列里选择另一个goroutine来执行。一旦被阻塞的系统调用执行完成并返回,对应的goroutine会放回到本地运行队列,而之前的线程会保存好,以便之后可以继续使用。
并发不是并行。并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,而并发是同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。

二、goroutine
下面来看一个样例,创建两个goroutine以并发形式分别显示大写和小写的英文字母。

package main

import (
	"fmt"
	"runtime"
	"sync"
)

func main() {
	//分配一个逻辑处理器给调度器使用
	//GOMAXPROCS允许程序更改调度器可以使用的逻辑处理器的数量
	runtime.GOMAXPROCS(1)
	//wg用来等待程序完成、计数加2,表示要等待两个goroutine
	var wg sync.WaitGroup
	wg.Add(2)

	fmt.Println("Start Goroutines")

	//声明一个匿名函数,并创建一个goroutine
	go func() {
		//在函数退出时调用Done来通知main函数工作已经完成
		defer wg.Done()

		//显示三次字母表
		for count := 0; count < 3; count++ {
			for char := 'a'; char <= 'z'; char++ {
				fmt.Printf("%c ", char)
			}
		}
	}()

	go func() {
		//在函数退出时调用Done来通知main函数工作已经完成
		defer wg.Done()

		for count := 0; count < 3; count++ {
			for char := 'A'; char <= 'Z'; char++ {
				fmt.Printf("%c ", char)
			}
		}
	}()

	//等待goroutine结束
	fmt.Println("Waiting To Finish")
	wg.Wait()

	fmt.Println("\nTerminating Program")
}

三、竞争状态
如果两个或者多个goroutine在互相没有同步的情况下,访问某个共享的资源,并试图同时读和写这个资源,就处于相互竞争的状态,这种情况被称作竞争状态。所以对一个共享资源的读和写操作必须是原子化的。

四、锁住共享资源
Go语言提供了传统的同步goroutine的机制,就是对共享资源加锁。如果需要顺序访问一个整型变量或者一段代码,atomic和sync包里的函数提供了很好的解决方案。
1、原子函数
原子函数能够以很底层的加锁机制来同步访问整型变量和指针。下面是一个使用原子函数修正竞争状态的示例。

package main

import (
	"fmt"
	"runtime"
	"sync"
	"sync/atomic"
)

var (
	//counter是所有goroutine都要增加其值的变量
	counter int64
	//wg用来等待程序结束
	wg sync.WaitGroup
)

func main() {
	//计数加2,表示要等待两个goroutine
	wg.Add(2)

	//创建两个goroutine
	go incCounter(1)
	go incCounter(2)

	//等待goroutiine结束
	wg.Wait()
	//显示最终的值
	fmt.Println("Final Counter:", counter)
}

func incCounter(id int) {
	defer wg.Done()

	for count := 0; count < 2; count++ {
		//安全地对counter加1
		//AddInt方法强制同一时刻只能有一个goroutine运行并完成这个加法操作
		//类似的函数还有LoadInt64,StoreInt64
		atomic.AddInt64(&counter, 1)
		//当前goroutine从线程退出,并放回到队列
		runtime.Gosched()
	}
}

2、互斥锁
互斥锁用于在代码上创建一个临界区,保证同一时间只有一个goroutine可以执行这个临界区代码。

func incCounter(id int) {
	defer wg.Done()

	for count := 0; count < 2; count++ {
		//同一时刻只允许一个goroutine进入
		mutex.Lock()
		{
			//捕获counter的值
			value := counter
			//当前goroutine从线程退出,并放回队列
			runtime.Gosched()
			//增加本地value值
			value++
			//保存回counter
			counter = value
		}
		mutex.Unlock()
		//释放锁,允许其他正在等待的goroutine
		
	}
}

对counter的操作在Lock()和Unlock()函数调用定义的临界区里被保护起来。使用大括号不是必须的。同一时刻只有一个goroutine可以进入临界区。

五、通道
除了上述方法,你还可以使用通道,通过发送和接收需要共享的资源,在goroutine之间做同步。

使用make创建通道

//无缓冲的整型通道
unbuffere := make(chan int)
//有缓冲的字符串通道
buffered := make(chan string, 10)

向通道发送值

//有缓冲的字符串通道
buffered := make(chan string, 10)
//通过通道发送一个字符串
buffered <- "Gopher"
//从通道接受值
value := <-buffered

1、无缓冲的通道指接受前没有能力保存任何值的通道。这种类型的通道强制要求goroutine之间必须同时完成发送和接收。
2、有缓冲的通道是一种在被接收前能存储一个或多个值的通道。这种类型的通道并不强制要求goroutine之间必须同时完成发送和接收。

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

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

相关文章

对考研考公的过分执念,正在悄悄束缚你的职场选择!

随着近年来就业形势的严峻&#xff0c;越来越多的同学在找工作时碰壁&#xff0c;尤其是对于大部分应届生&#xff0c;这种现象尤为明显。 每年数百万的大学生进入到社会&#xff0c;却发现能选择的机会并不多。高等教育规模不断扩大的背景下&#xff0c;职场晋升的门槛越来越…

Hudi最流行数据湖框架介绍

目录 1. 第一章Hudi 框架概述1.1 数据湖Data Lake1.1.1 仓库和湖泊1.1.2 什么是数据湖1.1.3 数据湖的优点1.1.4 Data Lake vs Data warehouse1.1.5 数据湖框架1.1.5.1 Delta Lake1.1.5.2 Apache Iceberg1.1.5.3 Apache Hudi 1.1.6 湖仓一体&#xff08;Data Lakehouse&#xff…

【1】从零开始学习目标检测:YOLO算法详解

从零开始学习目标检测&#xff1a;YOLO算法详解 文章目录 从零开始学习目标检测&#xff1a;YOLO算法详解1. &#x1f31f;什么是目标检测?2.&#x1f31f;传统的目标检测与基于深度学习的目标检测3.&#x1f31f;目标检测算法的工作流程4.&#x1f31f;目标检测可以干什么&am…

拿到新的服务器必做的五件事(详细流程,开发必看)

目录 1. 配置免密登录 基本用法 远程登录服务器&#xff1a; 第一次登录时会提示&#xff1a; 配置文件 创建文件 然后在文件中输入&#xff1a; 密钥登录 创建密钥&#xff1a; 2.部署nginx 一、前提条件 二、安装 Nginx 3.配置python虚拟环境 1.安装虚拟环境 …

自习室管理系统的设计与实现(论文+源码)_kaic

摘要 近年来&#xff0c;随着高校规模的逐步扩大&#xff0c;学生对高校自习室座位的需求也在不断增加。然而&#xff0c;一些高校仍然采用人工管理学院自习室座位&#xff0c;这大大降低了管理效率。显然&#xff0c;开发一个成本低、占用资源少、能提高高校自习室座位管理效率…

WindowsHash简介及windows认证

Windows系统使用两种方法对用户的密码进行哈希处理&#xff0c;他们分别是LAN Manager(LM)哈希和NT LAN Manager(NTML)哈希。 现在已经有了更新的NTLMv2以及Kerberos验证体系。 Windows的系统密码hash默认情况下一般由两个部分组成&#xff1a;第一部分是LM-hash&#xff0c;…

Nginx中间件漏洞复现

Nginx 解析漏洞 该漏洞与nginx、php版本无关&#xff0c;属于用户配置不当造成的解析漏洞。 漏洞原理&#xff1a; 该解析漏洞是PHP fastcgi 的漏洞&#xff0c;在PHP的配置文件 php.ini 中有一个关键的选项 cgi.fix_pathinfo 默认值为1&#xff0c;表示开启。同时在 php-fp…

ASO优化之如何回复Google Play评论

应用的平均评分会影响 Google Play 商店优化 和应用的 Google Play 排名。应用的评分越高&#xff0c;我们在搜索结果中的排名就越靠前。因此&#xff0c;当应用处于 4 星评级范围内时&#xff0c;它会被更多 Google Play 商店的访问者看到和发现。我们可以使用应用雷达中的评级…

Linux进程通信:有名管道

有名管道&#xff1a; 无名管道只能用于有亲缘关系的进程间通信。 因此提出有名管道&#xff08;也叫FIFO文件&#xff09;&#xff0c;以实现无亲缘关系进程间的通信。 不同于无名管道&#xff0c;有名管道FIFO文件的形式存在于文件系统&#xff0c;与一个路径名关联&#xff…

【复杂网络建模】——Python可视化重要节点识别(PageRank算法)

目录 一、复杂网络建模 二、建模的算法 三、使用PageRank算法进行网络重要节点识别 1、PageRank算法 2、基于PageRank算法的ER网络重要节点识别 3、基于PageRank算法的小世界网络重要节点识别 4、基于PageRank算法的无标度网络的重要节点识别 四、ER网络、小世界网络、…

春秋云境:CVE-2022-24663(远程代码执行漏洞exp)

目录 一、题目 二、构造exp执行php 三、蚁剑连接 一、题目 介绍&#xff1a; 远程代码执行漏洞&#xff0c;任何订阅者都可以利用该漏洞发送带有“短代码”参数设置为 PHP Everywhere 的请求&#xff0c;并在站点上执行任意 PHP 代码。P.S. 存在常见用户名低权限用户弱口令 …

华为OD机试真题(Java),开元音统计(100%通过+复盘思路)

一、题目描述 相对开音节构成的结构为辅音元音(aeiou)辅音(r除外)e&#xff0c;常见的单词有bike cake&#xff0c;给定一个字符串&#xff0c;以空格为分隔符。 反转每个单词的字母&#xff0c;若单词中包含如数字等其他非字母时不进行反转&#xff0c;反转后计算其中含有相对…

苹果手机屏幕上的圆点怎么设置?(开启悬浮按钮)

案例&#xff1a;苹果手机屏幕上的圆点怎么设置&#xff1f; 【求助&#xff01;苹果手机的小圆点怎么调出来&#xff1f;就是悬浮按钮那个。】 如果您是苹果手机的用户&#xff0c;您可能会在手机屏幕上看到一个小圆点&#xff0c;它可以让您方便地进行操作。这个圆点是 Assi…

TortoiseSVN使用-合并深度介绍

文章目录 3.6 合并深度介绍 本人其他相关文章链接 3.6 合并深度介绍 Working copy(工作副本)&#xff1a;即你当前的工作目录&#xff0c;一般默认为这个选项&#xff1b;Recursively(递归)&#xff1a;即你选择的目录的版本库&#xff0c;包括了其下面的子文件&#xff0c;子文…

叶黄素的17种功效与副作用(5点使用禁忌请小心)

叶黄素&#xff08;Lutein&#xff09;及其同分异构体玉米黄质&#xff08;zeaxanthin&#xff09;和内消旋玉米黄质&#xff08;meso-zeaxanthin&#xff09;是一种聚集在人类视网膜中的黄斑色素。 它们不能在哺乳动物体内合成&#xff0c;必须从饮食中获得&#xff0c;然后分…

智能家居工厂模式整体设计框架控制设备测试

通俗理解的步骤就是链表通用模板定义&#xff08;在头文件里定义&#xff09;、链表的创建&#xff08;头插尾插&#xff0c;在.C 文件里&#xff09;、链表的初始化&#xff08;init配置管脚初始电平等&#xff09;、链表内容的读取&#xff08;指令工厂TCP服务端读取客户端发…

【芝士总结】史上最详循环结构讲解(蒟蒻也能学会)

虽然计算机可以在短时间批量处理成千上万条指令&#xff0c;但是不少问题中有许多规律性的重复操作&#xff0c;比如说计算几百个学生的平均分&#xff0c;或者对上万人的名单进行排序。仅使用顺序或者分支结构&#xff0c;对每一步操作都写出对应的语句是不可能的&#xff1b;…

如何用链表实现LRU缓存淘汰算法

链表学习 一、 缓存1.1缓存介绍1.2 缓存策略 二、链表结构2.1 单链表2.2 循环链表2.3 双向链表2.4 双向循环链表2.5 链表与数组性能对比 三、如何基于链表实现LRU缓存淘汰算法 一、 缓存 1.1缓存介绍 缓存是一种提高数据读取性能的技术&#xff0c;在硬件设计、软件开发中都有…

参数与非参数检验:理解差异并正确使用

数据科学是一个快速发展的领域&#xff0c;它在很大程度上依赖于统计技术来分析和理解复杂的数据集。这个过程的一个关键部分是假设检验&#xff0c;它有助于确定从样本中获得的结果是否可以推广到总体。 在这篇文章中&#xff0c;我们将探讨参数与非参数检验之间的区别&#…

微信小程序对接在线客服系统,对接小程序订阅消息模板,小程序订阅方法以及后端发送订阅模板消息的方法...

微信小程序想要对接独立在线客服系统&#xff0c;除了使用小程序消息推送接口外&#xff0c;还可以使用webview嵌入的形式嵌入聊天链接。 但是&#xff0c;使用webview嵌入的形式&#xff0c;当用户离开页面以后&#xff0c;就收不到客服回复的消息了 所以&#xff0c;我们需要…