Go 并发编程

news2024/9/27 10:09:43

并发编程

1.1 并发与并⾏
在这里插入图片描述
并⾏与并发是两个不同的概念,普通解释:

  • 并发:交替做不同事情的能⼒
  • 并⾏:同时做不同事情的能⼒

如果站在程序员的⻆度去解释是这样的:

  • 并发:不同的代码块交替执⾏
  • 并⾏:不同的代码块同时执⾏

并发和并⾏都是为了提⾼机器硬件利⽤率,提升应⽤运⾏效率。并⾏和并发就是为达⽬的的两个⼿段。
并⾏可以提升CPU核⼼数,并发则是通过系统设计,⽐如分时复⽤系统,多进程,多线程的开发⽅法,
不过在对并发的⽀持上,Go语⾔是天⽣最好的,因为它设计的理念就是并发,⽽且是在语⾔层⾯实现的
并发。

1.2 Goroutine
如果对线程和进程有所了解的话,我们可以这样给进程和线程下⼀个专业点的定义。

  • 线程是最⼩的执⾏单位
  • 进程是最⼩的资源申请单位

在Go语⾔当中,有⼀个存在是⽐线程还要⼩的执⾏单位,那就是Goroutine,翻译上习惯叫例程或协
程,不过我们遵循原汁原味还是直接⽤Goroutine来称呼它。Go语⾔开发者为了实现并⽀持
Goroutine,特意花了⼤⼒⽓来开发⼀个语⾔层⾯的调度算法。
具体调度算法详情介绍可以查看英⽂原⽂:调度算法链接

简单理解的话,⾸先要明确Go语⾔调度算法中提到的三个字⺟,M,P,G,分别代表线程,上下⽂以
及Goroutine。
在这里插入图片描述
在操作系统层⾯线程仍然是最⼩的执⾏单位,在进程调度的时候,CPU需要在不同进程间切换,此时需
要保留运⾏的上下⽂信息,也就是前⾯提到的P,⾄于G则是代表了Go语⾔当中的Goroutine。

在这里插入图片描述
每个线程M有⼀个⾃⼰的上下⽂P,同⼀时刻,每个线程内部可以执⾏⼀个Groutine代码,如上图所
示,每个线程有⼀个⾃⼰的调度队列,这个队列⾥存放的就是若⼲个Goroutine。

在这里插入图片描述
当线程发⽣系统调⽤时,该线程会被阻塞,此时为了提⾼运⾏效率,Goroutine调度算法会将该阻塞的
线程Goroutine队列转移到其他线程上。当线程执⾏完系统调⽤后,⼜会从其他线程的队列中“借”⼀些
Goroutine过来。

1.3 Goroutine启动
在⼀台主机上,线程启动的数量是有上限的,这个上限并⾮可以启动的上限,⽽是不影响系统性能的上
限。Goroutine在并发上则没有这⽅⾯的顾虑,可以随⼼所欲的启动,不⽤担⼼数量的问题,当然这归
功于Go语⾔的调度算法。
那么Goroutine如何启动呢?其实在我们之前写的代码中,都存在⼀个Goroutine,就是我们的主函
数,我们习惯叫他main-goroutine。如果我们想再启动⼀个Goroutine,也⾮常容易,直接关键字go再
加上函数调⽤就⾏了。

go call_func()
package main

import "fmt"

func main() {
	fmt.Println("begin call goroutine")
	//启动goroutine
	go func() {
		fmt.Println("I am a goroutine!")
	}()
	fmt.Println("end call goroutine")
}

执⾏这个代码,我们⼤概率是看不到“I am a goroutine!”这句话的,因为go func()这⾥创建的Goroutine
或者尚未创建成功时,main-goroutine已经结束执⾏了,main-goroutine结束执⾏,也就代表着进程
退出,那么不会有任何代码被执⾏了!那么⼤家考虑⼀下,如何解决这个问题呢?

1.5 Go语⾔运⾏时
所谓运⾏时,就是运⾏的时刻,Go语⾔的运⾏时就是描述与进程运⾏相关的信息,我们可以使⽤
runtime包来显示⼀些运⾏时的信息。

1.5.1 GOMAXPROCS

func GOMAXPROCS(n int) int
//当n<=1时,查看当前进程可以并⾏的goroutine最⼤数量(CPU核⼼数)
//当n>1时,代表设置可并⾏的最⼤goroutine数量

这个函数可以帮我们查看或设置当前进程的最⼤CPU核⼼数。

2. 同步
同步在不同的语境代表不同的含义,在数据库中是指数据的同步,在分布式系统中是指系统内的数据⼀
致,⽽在语⾔层⾯的同步是指运⾏时步调⼀致,避免竞争,有先有后。

2.1 如何做到同步
为了提⾼CPU的使⽤效率,我们需要启动多个Goroutine,⽽多个Goroutine⽐线程的颗粒度还⼩,他
们之间必然存在争抢同⼀资源的现象,就像我们在线程中要控制同步⼀样,多个Goroutine在访问同⼀
共享资源时,我们仍然要控制同步。

如何做到最直接的同步,可以借鉴我们⽣活中的例⼦,在⽕⻋上我们去卫⽣间的时候,都会把⻔锁上,
这样别⼈就没法进来了。在这个例⼦中,我们和其他⼈就是Goroutine,⽽卫⽣间就是那个共享资源,
我们不允许发⽣⼤家⼀起进⼊使⽤的情况,⽽解决这个问题的关键就是锁!
那么Go语⾔给我们提供了哪些同步机制呢?主要有如下⽅式:

  • WaitGroup 计数等待法
  • Once 执⾏⼀次
  • Mutex 互斥锁
  • RWMutex 读写锁
  • Cond 条件变量
  • channel 通道,Go语⾔的最⼤特性

在上述⽅法中,除了channel,其余的同步⽅式都在Go语⾔的sync包当中。

2.2 WaitGroup
Go语⾔的同步⽅式很多,WaitGroup的实现⽅式很巧妙,主要只有3个API。

//增加计数
func (wg *WaitGroup) Add(delta int)
//减少计数
func (wg *WaitGroup) Done()
//阻塞等待计数变为0
func (wg *WaitGroup) Wait()

它的核⼼思想是当启动⼀个Goroutine时,使⽤Add添加⼀个计数器,⽽Wait的功能是阻塞等待计数器
归0,Done的作⽤则是Goroutine运⾏结束后执⾏此句话清掉计数器的⼀个计数。
利⽤WaitGroup的特性,我们可以优雅的实现⼀个例⼦:启动10个Goroutine,让他们顺序退出,
main-goroutine等待所有Goroutine退出后才可退出。

package main

import (
	"fmt"
	"sync"
	"time"
)

var w sync.WaitGroup

func main() {
	for i := 0; i < 10; i++ {
		w.Add(1) //添加⼀个要监控的Goroutine数量
		go func(num int) {
			time.Sleep(time.Second * time.Duration(num))
			fmt.Printf("I am %d Goroutine\n", num)
			w.Done() //释放⼀个
		}(i)
	}
	w.Wait() //阻塞等待
}

I am 0 Goroutine
I am 1 Goroutine
I am 2 Goroutine
I am 3 Goroutine
I am 4 Goroutine
I am 5 Goroutine
I am 6 Goroutine
I am 7 Goroutine
I am 8 Goroutine
I am 9 Goroutine

2.3 Mutex互斥锁
提到互斥锁,我们通常会提到⼀个临界区的概念,Goroutine在准备访问共享数据时,我们就认为它进
⼊了临界区。
使⽤互斥锁的核⼼思想就是进⼊临界区之前先要申请锁,申请到锁的Goroutine继续执⾏,⽽没有申请
到的Goroutine则阻塞等待别⼈释放这个mutex,这样就可以有效的控制Goroutine之间竞争的问题。
下述代码就是⼀个存在数据修改竞争的例⼦,循环1000次对⼀个数据⾃增,⽬标的输出结果是1000,
但执⾏下⾯的代码很难获得1000。

package main

import (
	"fmt"
	"sync"
)

var x = 0

func increment(wg *sync.WaitGroup) {
	x = x + 1
	wg.Done()
}
func main() {
	var w sync.WaitGroup
	for i := 0; i < 1000; i++ {
		w.Add(1)
		go increment(&w)
	}
	w.Wait()
	fmt.Println("final value of x", x)
}

final value of x 974

上述代码如果在进⼊临界区前使⽤mutex,就可以很好的解决该问题。代码主要修改increment函数即
可:

func increment(wg *sync.WaitGroup) {
 mutex.Lock() //上锁
 x = x + 1 //临界区
 mutex.Unlock() //释放锁
 wg.Done()
}

修改后再执⾏代码,就可以很好的看到效果。

final value of x 1000

2.4 RWMutex

RWMutex我们可以称其为读写锁,它算是mutex的改进版,因为mutex的特点是排他性,只要有⼀个
上锁成功了,其余⼈都不可以使⽤。但在实际开发过程中,经常会出现多个Goroutine去访问相同的共
享资源,只不过这些Goroutine中有些是读数据,有些是写数据。开发者肯定明⽩,读数据不会对数据
造成影响,这样理论上来说,⼀个读的Goroutine上锁了,其余的读Goroutine理应也可以访问,这样
就出现了读写锁。对于读写锁来说,关键是掌握它的原则:

  • 读共享
  • 写独占
  • 写优先级⾼
package main

import (
	"fmt"
	"sync"
	"time"
)

var rwlock sync.RWMutex
var wg sync.WaitGroup
var x = 0

func go_reader(num int) {
	for {
		rwlock.RLock()
		fmt.Printf("I am %d reader goroutine x = %d\n", num, x)
		time.Sleep(time.Millisecond * 2)
		rwlock.RUnlock()
	}
	wg.Done()
}
func go_writer(num int) {
	for {
		rwlock.Lock()
		x += 1
		fmt.Printf("I am %d writer goroutine x = %d\n", num, x)
		time.Sleep(time.Millisecond * 2)
		rwlock.Unlock()
	}
	wg.Done()
}
func main() {
	wg.Add(10)
	for i := 0; i < 7; i++ {
		go go_reader(i)
	}
	for i := 0; i < 3; i++ {
		go go_writer(i)
	}
	wg.Wait()
}

2.5 Once
在很多时候,我们会有⼀个需求,那就是虽然多个Goroutine都要运⾏⼀段代码,但我们却希望这段代
码只能被⼀个Goroutine运⾏,也就是说只被允许运⾏⼀次。在Go语⾔当中,就给我们提供了这样的机
制 – Once。

package main

import (
	"fmt"
	"sync"
)

func main() {
	var count int
	var once sync.Once
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			once.Do(func() {
				count += 1 //确保只执⾏⼀次
			})
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Printf("Count is %d\n", count)
}

Count is 1

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

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

相关文章

慢 SQL 的致胜法宝

大促备战&#xff0c;最大的隐患项之一就是慢SQL&#xff0c;对于服务平稳运行带来的破坏性最大&#xff0c;也是日常工作中经常带来整个应用抖动的最大隐患&#xff0c;在日常开发中如何避免出现慢SQL&#xff0c;出现了慢SQL应该按照什么思路去解决是我们必须要知道的。本文主…

C++对象模型(5)-- 数据语义学:继承的对象布局(不含虚函数)

1、单继承的对象布局 (1) 在普通继承&#xff08;没有虚函数、没有继承虚基类&#xff09;的情况下&#xff0c;按父对象、子对象的顺序布局 我们来看下面的例子&#xff1a; class Base { protected:int x;int y; };class Derive : public Base { private:int z; };int mai…

vue2项目中使用element ui组件库的table,制作表格,改表格的背景颜色为透明的

el-table背景颜色变成透明_el-table背景透明_讲礼貌的博客-CSDN博客 之前是白色的&#xff0c;现在变透明了&#xff0c;背景颜色是蓝色

短视频矩阵系统源码--源头技术独立自研框架开发

目录 一、批量剪辑&#xff08;采用php语言&#xff0c;数学建模&#xff09; 短视频合成批量剪辑的算法主要有以下几种&#xff1a; 1. 帧间插值算法&#xff1a;通过对多个视频的帧进行插帧处理&#xff0c;从而合成一段平滑的短视频。 2. 特征提取算法&#xff1a;提取多…

RedissonClient中Stream流的简单使用

1、pub端 //获取一个流 RStream rStream redissonClient.getStream("testStream"); //创建一个map&#xff0c;添加数据 Map<String, Object> rr new HashMap<>(); rr.put("xx", RandomUtil.randomString(5)); //添加到流 rStream.addAll(r…

TypeScript 笔记:String 字符串

1 对象属性 length 返回字符串的长度 2 对象方法 charAt() 返回在指定位置的字符 charCodeAt() 返回在指定的位置的字符的 Unicode 编码 concat 连接两个或更多的字符串 indexOf 返回某个指定的字符串值在字符串中首次出现的位置 lastIndexOf 从后向前搜索字符串&…

c语言练习题83:#include“ “和#include<>的区别

#include" "和#include<>的区别 #include<> 默认根据环境变量的值去先搜索标准库&#xff0c;搜索系统文件会比较快。 #include“” 先搜索当前工程的路径&#xff0c;搜索自己自定义的文件会比较快。 因此自定义的头文件的名称包含在<>中的话…

LeetCode 24.两两交换链表中的结点

题目链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目解析 首先可以特判一下&#xff0c;如果结点数目小于等于1&#xff0c;则直接返回即可&#xff0c;因为数目小于等于1就不需要交换了。 然后我们可以创建一个虚拟的头结点&#xff0c;然…

SpringCloud源码探析(十)-Web消息推送

1.概述 消息推送在日常使用中的场景比较多&#xff0c;比如有人点赞了我的博客或者关注了我&#xff0c;这时我就会收到一条推送消息&#xff0c;以此来吸引我点击或者打开应用。消息推送的方式主要分为两种&#xff1a;web消息推送和移动端消息推送。它将所要发送的信息&…

【物联网】Arduino+ESP8266物联网开发(一):开发环境搭建 安装Arduino和驱动

ESP8266物联网开发 1.开发环境安装 开发软件下载地址&#xff1a; 链接: https://pan.baidu.com/s/1BaOY7kWTvh4Obobj64OHyA?pwd3qv8 提取码: 3qv8 1.1 安装驱动 将ESP8266连接到电脑上&#xff0c;安装ESP8266驱动CP210x 安装成功后&#xff0c;打开设备管理器&#xff0c…

抖音seo源代码开源部署----基于开放平台SaaS服务

抖音SEO搜索是什么&#xff1f; 抖音SEO搜索是指在抖音平台上进行搜索引擎优化&#xff08;Search Engine Optimization&#xff09;的一种技术手段。 通过优化抖音账号、发布内容和关键词等&#xff0c;提高抖音视频在搜索结果中的排名&#xff0c;从而增加视频曝光量和用户点…

什么是全流程的UI设计?它与单页面的视觉设计有什么区别?

在软件产品研发流程中&#xff0c;产品交互设计一般是根据项目需求&#xff0c;做出设计方案&#xff0c;以求解决某个问题。而全流程的设计不再局限于短暂或者单个页面的视觉优化&#xff0c;而是追求持续性地参与&#xff0c;以全局性整体性地提升产品体验。 在软件内的信息传…

mybatis配置entity下不同文件夹同类型名称的多个类型时启动springboot项目出现TypeException源码分析

记录问题&#xff1a;当配置了 mybatis.type-aliases-packagecom.runjing.erp.entity 配置项时&#xff0c;如果entity文件夹下存在不同子文件夹下的同名类型时&#xff0c;mybatis初始化加载映射时会爆出org.apache.ibatis.type.TypeException&#xff1a; The alias TestDemo…

怎么将自己拍摄的视频静音?详细步骤教会你~

大部分人都会遇到的一个问题&#xff0c;我们在拍摄视频时容易将嘈杂的背景音或环境音录进去&#xff0c;怎样解决这个问题呢&#xff1f;今天就来教大家具体操作步骤&#xff0c;只需用到这个软件即可&#xff01; 第一步&#xff1a;打开我们的【音分轨】APP&#xff0c;进入…

山西电力市场日前价格预测【2023-10-10】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-10-10&#xff09;山西电力市场全天平均日前电价为555.24元/MWh。其中&#xff0c;最高日前电价为1130.92元/MWh&#xff0c;预计出现在18: 30。最低日前电价为293.94元/MWh&#xff0c;预…

网络流量安全分析-工作组异常

在网络中&#xff0c;工作组异常分析具有重要意义。以下是网络中工作组异常分析的几个关键点&#xff1a; 检测网络攻击&#xff1a;网络中的工作组异常可能是由恶意活动引起的&#xff0c;如网络攻击、病毒感染、黑客入侵等。通过对工作组异常的监控和分析&#xff0c;可以快…

【SHUD】SHUD模型Windows下的编译过程

目录 〇、绪论一、SHUD水文模型说明二、SHUD编译1、SUNIALS库的编译1.1. 使用MSVC1.1.1 CMAKE 1.2. 使用MINGW1.2.1 CMAKE1.2.2 生成库文件 2、SHUD的编译过程2.1. LINUX和MAC环境2.2. WINDOWS环境2.2.1 shud2.2.2 shud_omp 〇、绪论 科学模型依据有限时空内的观测&#xff0c…

华为OD机试 - 数字反转打印(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

3D全景虚拟样板间展销系统扩展用户市场范围

VR样板间&#xff0c;能够真实还原现场&#xff0c;定制需要的场景。让一切比真实更真实。用户可以720度看房&#xff0c;自由行走在空间里&#xff0c;直观感受各空间的大小&#xff0c;看到自己家中的“未来样子”&#xff0c;同时通过操控手柄&#xff0c;控制整个智能家居系…

Redis-04独立功能的实现

1、发布与订阅 介绍&#xff1a; Redis的发布与订阅功能由PUBLISH、SUBSCRIBE、PSUBSCRIBE等命令组成。通过SUBSCRIBE命令&#xff0c;客户端可以订阅一个或多个频道&#xff0c;成为这些频道的订阅者&#xff08;subscriber&#xff09;每当有其他客户端向被订阅的频道发送消…