一文搞懂Go语言并发【goroutine】

简介: 一文搞懂Go语言并发【goroutine】

文章目录



前言


介绍Go语言并发底层原理,以及对比Go语言并发与其他语言并发的优劣



一、基本概念


并发、并行区分


1.概念


  并发:同一时间段内一个对象执行多个任务,充分利用时间
  并行:同一时刻,多个对象执行多个任务


2.图解


类似于超市柜台结账,并行是多个柜台结多个队列,在计算机中是多核cpu处理多个go语言开启的线程,并发是一个柜台结账多个队列,在计算机中就是单核cpu处理多个任务,抢夺时间片.

92da2a04b4c24a2d86b76175955c947c.png

②从用户态线程,内核态线程阐述go与java并发的优劣


1.用户态线程、内核态线程差异:


用户态:只能受限制的访问内存,且不允许访问外围设备,占用CPU资源可以被其他程序抢走。
内核态:CPU可以访问内存所有数据,包括外围设备,例如硬盘网卡等,cpu可以将自己从一个程序切换到另一个程序


2.java与go并发差异:


java:
  java没有规定具体使用什么线程,而是在不同形态的线程上进行切换,会耗费相当的资源
  go是用户态线程,资源耗费较少,一个线程的栈体默认为1M,并且需要运行在JVM上
go:
  go语言并发通过,goroutine实现,属于用户态的线程,可以根据需要创建成千上万个
  goroutine,每个goroutine占用内存大小会根据需要动态生成,典型的大小为2kB可
  以按需求放大到1GB,在go语言中一次可以轻松创建十万左右的goroutine,并且不
  依赖运行环境。


②高并发为什么是Go语言强项?


1.历史背景


  Go语言产生较晚,在其产生之前就已经有了多核cpu,所以设计者的理念就是将这门新的语言使用到
  多核cpu上支持更大数量级的并发


2.自身原因


  Go语言多并发底层实现使用的是协程,他占有更少的资源具有更快的执行速度,占用的资源还会根据
  任务量进行扩大或者缩小


③Go语言实现高并发底层GMP模型原理解析


1. G:


G是Goroutine的缩写,在这里就是Goroutine的控制结构,是对Goroutine的抽象。
其中包括执行的函数指令及参数;G保存的任务对象;线程上下文切换,现场保护
和现场恢复需要的寄存器(SP、IP)等信息。在 Go 语言中使用 runtime.g 结构表示。


2. M:


表示操作系统线程也可以称为内核线程,由操作系统调度以及管理,调度器最多可
以创建 10000 个线程,在 Go 语言中使用 runtime.m 结构表示。(用户线程与内核线程的映射关系)


3. P:


调度各个goroutine,使他们之间协调运行逻辑处理器,但不代表真正的CPU的数量,
真正决定并发程度的是P,初始化的时候一般会去读取GOMAXPROCS对应的值,如果没有显示设置,
则会读取默认值,在Go1.5之后GOMAXPROCS被默认设置可用的核数,而之前则默认为1,在 Go 语
言中使用 runtime.p 结构表示。


4.指定cpu线程个数


  通过runtime.GOMAXPROCS(),可以指定P的个数,如果没有指定则默认跑满整个cpu


二、上代码学会Go语言并发


①.开启一个简单的线程


  开启线程使用go+函数
  以下案例要认识到开启多线程使用函数闭包可能会出现的问题


1.使用匿名函数开启线程


//打印1-1000
  for i := 0; i < 1000; i++ {
    go func() {
      fmt.Println(i)
    }()
  }
//这里使用了函数闭包
/*
打印结果
  995
  995
  995
  996
  996
  999
  1000
  1000
*/


2.出问题的原理:


匿名函数进行操作时会将当前环境内的变量进行闭包,由于启动线程需要一定时间

在启动线程的时候i进行了改变所以打印的时候会有许多值相同


②.动态的关闭线程


1.为什么需要进行动态的关闭线程?


  在Go语言中如果不进行动态的关闭线程,那么有可能在子线程没有执行结束主线程就结束了,那样的话会有
  程序安全隐患,所以主线程不可以直接结束,应作为后盾,直到所有线程都结束了才可以结束。


2.使用waitGroup


  waitGroup有三个方法常用
    waitGroup.Add():使用wait计数器记1次数//将创建的线程数传进去
    waitGroup.Done():wait计数器减1(放在被开启线程的函数内)
    waitGroup.Wait():阻塞等待wait计数器值为零(放在主线程内)
    defer是在函数主体执行完的时候执行的代码(可理解为延时执行)


代码如下:


package main
import (
  "fmt"
  "math/rand"
  "sync"
  "time"
)
// 定义一个waitGroup结构体变量
var wg sync.WaitGroup
func f(i int) {
  // 等到函数执行完毕,会将waitGroup内的计数器减一
  defer wg.Done()
  rand.Seed(time.Now().UnixNano())
  time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
  fmt.Println(i)
}
func main() {
  for i := 0; i < 100; i++ {
    // 开启一个线程就使用waitGroup记一次数
    wg.Add(1)
    go f(i)
  }
  // 阻塞等待waitGroup计数器为0
  wg.Wait()
  fmt.Println("hello")
}


总结


Go语言的高并发奠定了其未来在高级语言中的地位,越来越多的用户加入互联网需要一个支持高并发语言的支持,亿万级电商秒杀,亿万级游戏用户同时在线都离不开这样的语言,所以Go语言一直被游戏后端、web后端项目开发者青睐。

目录
相关文章
|
1天前
|
安全 Java Go
探索Go语言在高并发环境中的优势
在当今的技术环境中,高并发处理能力成为评估编程语言性能的关键因素之一。Go语言(Golang),作为Google开发的一种编程语言,以其独特的并发处理模型和高效的性能赢得了广泛关注。本文将深入探讨Go语言在高并发环境中的优势,尤其是其goroutine和channel机制如何简化并发编程,提升系统的响应速度和稳定性。通过具体的案例分析和性能对比,本文揭示了Go语言在实际应用中的高效性,并为开发者在选择合适技术栈时提供参考。
|
5天前
|
运维 Kubernetes Go
"解锁K8s二开新姿势!client-go:你不可不知的Go语言神器,让Kubernetes集群管理如虎添翼,秒变运维大神!"
【8月更文挑战第14天】随着云原生技术的发展,Kubernetes (K8s) 成为容器编排的首选。client-go作为K8s的官方Go语言客户端库,通过封装RESTful API,使开发者能便捷地管理集群资源,如Pods和服务。本文介绍client-go基本概念、使用方法及自定义操作。涵盖ClientSet、DynamicClient等客户端实现,以及lister、informer等组件,通过示例展示如何列出集群中的所有Pods。client-go的强大功能助力高效开发和运维。
24 1
|
6天前
|
SQL 关系型数据库 MySQL
Go语言中使用 sqlx 来操作 MySQL
Go语言因其高效的性能和简洁的语法而受到开发者们的欢迎。在开发过程中,数据库操作不可或缺。虽然Go的标准库提供了`database/sql`包支持数据库操作,但使用起来稍显复杂。为此,`sqlx`应运而生,作为`database/sql`的扩展库,它简化了许多常见的数据库任务。本文介绍如何使用`sqlx`包操作MySQL数据库,包括安装所需的包、连接数据库、创建表、插入/查询/更新/删除数据等操作,并展示了如何利用命名参数来进一步简化代码。通过`sqlx`,开发者可以更加高效且简洁地完成数据库交互任务。
13 1
|
5天前
|
算法 NoSQL 中间件
go语言后端开发学习(六) ——基于雪花算法生成用户ID
本文介绍了分布式ID生成中的Snowflake(雪花)算法。为解决用户ID安全性与唯一性问题,Snowflake算法生成的ID具备全局唯一性、递增性、高可用性和高性能性等特点。64位ID由符号位(固定为0)、41位时间戳、10位标识位(含数据中心与机器ID)及12位序列号组成。面对ID重复风险,可通过预分配、动态或统一分配标识位解决。Go语言实现示例展示了如何使用第三方包`sonyflake`生成ID,确保不同节点产生的ID始终唯一。
go语言后端开发学习(六) ——基于雪花算法生成用户ID
|
6天前
|
JSON 缓存 监控
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
Viper 是一个强大的 Go 语言配置管理库,适用于各类应用,包括 Twelve-Factor Apps。相比仅支持 `.ini` 格式的 `go-ini`,Viper 支持更多配置格式如 JSON、TOML、YAML
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
|
3天前
|
NoSQL Go Redis
Go语言中如何扫描Redis中大量的key
在Redis中,遍历大量键时直接使用`KEYS`命令会导致性能瓶颈,因为它会一次性返回所有匹配的键,可能阻塞Redis并影响服务稳定性。为解决此问题,Redis提供了`SCAN`命令来分批迭代键,避免一次性加载过多数据。本文通过两个Go语言示例演示如何使用`SCAN`命令:第一个示例展示了基本的手动迭代方式;第二个示例则利用`Iterator`简化迭代过程。这两种方法均有效地避免了`KEYS`命令的性能问题,并提高了遍历Redis键的效率。
11 0
|
4天前
|
监控 Serverless Go
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
|
5天前
|
关系型数据库 MySQL 数据库连接
Go语言中使用sqlx来操作事务
在应用中,数据库事务保证操作的ACID特性至关重要。`github.com/jmoiron/sqlx`简化了数据库操作。首先安装SQLX和MySQL驱动:`go get github.com/jmoiron/sqlx`和`go get github.com/go-sql-driver/mysql`。导入所需的包后,创建数据库连接并使用`Beginx()`方法开始事务。通过`tx.Commit()`提交或`tx.Rollback()`回滚事务以确保数据一致性和完整性。
8 0
|
6天前
|
SQL 安全 关系型数据库
Go 语言中的 MySQL 事务操作
在现代应用中,确保数据完整与一致至关重要。MySQL的事务机制提供了可靠保障。本文首先解释了事务的概念及其ACID特性,随后介绍了如何在Go语言中使用`database/sql`包进行MySQL事务操作。通过一个银行转账的例子,演示了如何通过Go开启事务、执行操作并在必要时回滚或提交,确保数据一致性。最后,还讨论了不同事务隔离级别的含义及如何在Go中设置这些级别。通过本文的学习,开发者能更好地掌握MySQL事务的应用。
11 0
|
7天前
|
SQL 关系型数据库 MySQL
Go语言中进行MySQL预处理和SQL注入防护
在现代Web应用开发中,安全性至关重要。SQL注入是一种常见的攻击方式,攻击者可通过构造特殊SQL查询来非法访问或修改数据库数据。本文介绍如何利用Go语言中的预处理SQL语句来防范此类攻击。预处理不仅能提升安全性,还能提高性能并简化代码。通过使用`?`作为占位符,Go自动处理参数转义,有效避免SQL注入。此外,文章还提供了连接MySQL数据库、执行预处理查询以及最佳实践的示例代码。务必遵循这些指导原则,确保应用程序的安全性。
16 0