Golang的Goroutine(协程)与runtime

目录

Runtime 包概述

Runtime 包常用函数

1. GOMAXPROCS

2. Caller 和 Callers

3. BlockProfile 和 Stack

理解Golang的Goroutine

Goroutine的基本概念

特点:

Goroutine的创建与启动

示例代码

解释

Goroutine的调度

Gosched的作用

示例代码

输出

解释

Goroutine的性能优化

示例代码

解释

实际应用中的使用场景

总结

Runtime与Routine的关系

补充(代码)


Runtime 包概述

runtime 包提供了与 Go 运行时环境交互的功能,包括 goroutine 调度、内存管理、堆栈操作等。通过 runtime 包,我们可以更好地控制程序的运行行为。

Runtime 包常用函数

1. GOMAXPROCS

设置可以并行执行的最大 CPU 数,并返回之前的设置值。

fmt.Println(runtime.GOMAXPROCS(2))

2. Caller 和 Callers

runtime.Caller 和 runtime.Callers 用于获取调用栈信息。

pc, file, line, ok := runtime.Caller(1)  
if !ok {  fmt.Println("runtime.Caller() failed")  return  
}  
fmt.Printf("PC: %v, File: %s, Line: %d\n", pc, file, line)  

3. BlockProfile 和 Stack

runtime.BlockProfile() 用于获取阻塞概述,而 runtime.Stack() 则可以打印当前 goroutine 或所有 goroutine 的堆栈跟踪信息。


理解Golang的Goroutine

Goroutine是Golang语言中的轻量级线程,能够在单一操作系统线程上运行多个Goroutine,从而提高并发编程的效率。本文将通过实际的代码示例,详细讲解Goroutine的创建、调度、性能优化以及在实际应用中的使用场景。


    Goroutine的基本概念

    Goroutine 是Golang语言中用于并发编程的基本单位。与传统的线程(Thread)相比,Goroutine的调度和切换成本更低,因为它们是基于Golang的协作式调度模型设计的。每个Goroutine的栈大小默认为2KB,但随着其生命周期的变化,栈大小会自动扩大以适应需求。

    特点:

    • 轻量级:创建和销毁Goroutine的开销非常低。
    • 高并发:Golang可以轻松支持数万甚至更多的Goroutine同时运行。
    • ** Channels**:Goroutine之间通过Channel进行通信,避免了共享内存带来的竞态条件问题。

    Goroutine的创建与启动

    Goroutine的创建非常简单,只需要在函数调用前加上go关键字即可启动一个新的Goroutine。

    示例代码
    package mainimport ("fmt""runtime"
    )func print() {fmt.Println("这是一个新的Goroutine。")
    }func main() {go print()  // 启动一个新的Goroutinefmt.Println("主Goroutine结束。")  // 主Goroutine继续执行
    }
    

    解释

    • go print():启动一个新的Goroutine执行print函数。
    • 主Goroutine继续执行fmt.Println("主Goroutine结束。")
    • 需要注意的是,如果主Goroutine执行完毕,程序可能会直接退出,可能无法看到子Goroutine的输出。为了确保子Goroutine有足够的时间完成,可以通过time.Sleep()或者其他同步机制来控制。

    Goroutine的调度

    Goroutine的调度是由Golang的运行时环境负责的。与线程不同,Goroutine采用的是非抢占式调度,即只有在当前Goroutine主动让出CPU(例如调用runtime.Gosched())时,其他Goroutine才能获取执行机会。

    Gosched的作用

    runtime.Gosched()用于让出当前Goroutine的执行权限,允许其他Goroutine运行。

    示例代码
    package mainimport ("fmt""runtime"
    )func main() {go func() {for i := 1; i <= 5; i++ {runtime.Gosched()fmt.Println(i)}}()fmt.Println("主Goroutine结束。")
    }
    
    输出
    主Goroutine结束。
    1
    2
    3
    4
    5
    

    解释

    • 子Goroutine在循环中调用runtime.Gosched(),主动让出CPU时间片。
    • 主Goroutine在子Goroutine开始后立即执行并输出“主Goroutine结束。”。

    Goroutine的性能优化

    Goroutine的性能优化主要体现在以下几个方面:

    1. 设置GOMAXPROCS:通过runtime.GOMAXPROCS(n)设置可以并行执行的最大CPU核数。合理设置这个值可以提高程序的并行度。

      • 示例:runtime.GOMAXPROCS(2),表示最多同时使用2个CPU核心。
    2. 避免竞态条件:多个Goroutine访问共享变量时,可能会出现竞态条件。可以通过Channel或sync包中的同步原语(如Mutex、RWMutex)来避免。

    3. 合理分配任务:在高并发场景下,合理分配任务可以提高程序的执行效率。

    示例代码
    package mainimport ("fmt""math""runtime""sync""time"
    )func findPrime(num int, inChan chan int, primeChan chan int, exitChan chan bool) {var flag boolfor {v, ok := <-inChanif !ok {break}flag = truefor i := 2; i < int(math.Sqrt(float64(v))); i++ {if v % i == 0 {flag = falsebreak}}if flag {primeChan <- v}}exitChan <- true
    }func main() {num := 10000000  // 查找小于num的所有质数start := time.Now()inChan := make(chan int, num)primeChan := make(chan int, num)exitChan := make(chan bool, 4)  // 假设使用4个Goroutinego func() {for i := 2; i < num; i++ {inChan <- i}close(inChan)}()for i := 0; i < 4; i++ {go findPrime(num, inChan, primeChan, exitChan)}go func() {for i := 0; i < 4; i++ {<-exitChan}close(primeChan)}()count := 0for v := range primeChan {count++}fmt.Printf("找到%d个质数,用时:%v\n", count, time.Since(start))
    }
    

    解释

    • Channel通信:通过Channel传递数据,避免了共享变量带来的竞态条件。
    • 并行计算:通过启动多个Goroutine并行计算质数,提高了程序的执行效率。
    • 同步机制:通过exitChan等待所有Goroutine完成任务。

    实际应用中的使用场景

    1. 并发请求处理:在Web服务器中,通过Goroutine并行处理多个HTTP请求,提高吞吐量。
    2. 数据处理:在数据处理任务中,通过Goroutine并行处理不同的数据片段,提高处理速度。
    3. I/O密集型任务:在I/O密集型任务(如文件读写、网络通信)中,Goroutine可以通过非阻塞的方式提高资源利用率。

    总结

    Goroutine是Golang语言中的并行编程核心,具有轻量级、高效和灵活的特点。通过合理利用Goroutine,可以显著提高程序的性能和响应速度。在实际应用中,需要注意避免竞态条件,合理分配任务,并通过Channel等方式实现Goroutine之间的安全通信。


    Runtime与Routine的关系

    在Go语言中,runtimeroutine是两个密切相关但又有明确区别的概念。runtime主要指的是Go语言的运行时环境,它为Go程序的执行提供了必要的支持,如内存管理、Goroutine调度、错误处理等。而routine在Go语言中特指Goroutine,即轻量级的线程。

    具体来说:

    1. Runtime

      • 定义runtime是一个Go语言标准库中的包,提供了与程序运行时环境相关的功能。
      • 功能
        • Goroutine调度:管理Goroutine的创建、执行和调度。
        • 内存管理:负责内存的分配和回收,包括垃圾回收机制。
        • 错误处理:提供了一些与错误处理相关的函数,如runtime.Error
        • 与操作系统交互:处理程序运行时的环境,如线程数、CPU核数等。
        • 性能监控:提供了一些与性能监控相关的函数,如内存使用统计。
      • 常用函数
        • Gosched():让出当前Goroutine的执行权限,允许其他Goroutine运行。
        • GOMAXPROCS():设置或获取可以并行执行用户级处理的最大CPU核数。
        • NumCPU():返回当前可用的CPU核数。
        • Goexit():使当前Goroutine退出,然后调度器会打印当前的栈跟踪信息。
    2. Routine(Goroutine)

      • 定义:Goroutine是Go语言中的轻量级线性,它由Go的运行时环境管理。
      • 特点
        • 轻量级:创建和销毁Goroutine的开销比传统线程低。
        • 高并发:可以轻松支持数万甚至更多的Goroutine同时运行。
        • 非阻塞式调度:基于协作式调度,主动让出CPU时间片。
      • 创建方式:通过go关键字启动一个新的Goroutine。
      • 常见用途
        • 并发执行任务:将一个函数或方法转换为异步执行,不阻塞主Goroutine。
        • 处理I/O密集型任务:在I/O操作中,释放资源让其他Goroutine使用。
        • 并行计算:在多核CPU上并行执行任务以提高计算效率。
    3. 两者的关系

      • 调度管理runtime包负责管理Goroutine的调度,包括如何分配时间片、如何在多个CPU核间分配Goroutine等。
      • 资源管理:通过runtime包,可以控制Goroutine的数量和并行度,优化程序性能。
      • 协作式调度:Goroutine通过runtime.Gosched()主动让出CPU时间片,协作式调度依赖于Goroutine自身的配合,而不是操作系统强制切换。

    补充(代码)

    package mainimport ("fmt""math""runtime""time"
    )func PrintCallerInfo() {//返回调用栈的信息(文件名、行号等)。//参数:skip表示从调用Caller开始往回数多少层的调用栈信息。// 	skip = 0: 返回调用runtime.Caller的函数(即直接包含runtime.Caller(0)调用的那行代码)的文件名、行号等信息。// skip = 1: 返回调用runtime.Caller所在函数的直接调用者的文件名和行号。换句话说,就是调用了包含runtime.Caller(1)的函数的地方的调用栈信息。// skip = 2: 返回上一步调用者的调用者信息,依此类推。每增加1,就向上追溯一个调用栈帧。// 以此类推,随着skip值的增加,可以逐级向上追溯到更早的调用栈信息。如果指定的层级超出了实际存在的调用栈层数,则ok将为false,其他返回值(pc, file, line)将没有意义或为零值。pc, file, line, ok := runtime.Caller(1)if !ok {fmt.Println("runtime.Caller() failed")return}fmt.Printf("PC: %v, File: %s, Line: %d\n", pc, file, line)
    }func main() {// 设置可以并行执行的最大CPU数,并返回之前的设置值;控制了Go程序可以同时使用的操作系统线程数量。fmt.Println(runtime.GOMAXPROCS(2))// 启动一个协程go func(x int) {//time.Sleep(1 * time.Second)fmt.Println("Goroutine", x)}(1)// 终止go func(x int) {defer fmt.Println("Goroutine", x)//time.Sleep(1 * time.Second)runtime.Goexit() // 当前goroutine 退出fmt.Println("This won't print")}(2)// 当前程序中活跃的gotoutines数量fmt.Println("NumGoroutine:", runtime.NumGoroutine())// 让出当前goroutine的处理器,允许其他等待的goroutines运行;// 这是一个提示,而不是强制性的上下文呢切换go func() {for i := 1; i <= 5; i++ {runtime.Gosched()fmt.Println(i)}}()go func(x int) {//time.Sleep(1 * time.Second)fmt.Println("Goroutine", x)}(3)go func(x int) {//time.Sleep(1 * time.Second)fmt.Println("Goroutine", x)}(4)// 其它//runtime.BlockProfile() //获取当前的阻塞概要信息(block profile),它用于分析goroutine之间的同步问题,如锁竞争、通道阻塞等。//runtime.Caller() //返回调用栈的信息(文件名、行号等)。//runtime.Callers() //类似Caller,但返回的是多个调用栈帧的信息。//runtime.GC() //:手动触发垃圾回收。通常不需要手动调用,Go的垃圾收集器会自动管理内存。fmt.Println(runtime.GOROOT()) // 返回Go的安装目录//runtime.KeepAlive(x any) //确保对象在其作用域结束前不会被垃圾回收。//runtime.Stack() //将当前goroutine或所有goroutines的堆栈跟踪写入提供的缓冲区。// runtime.Caller//PrintCallerInfo()//time.Sleep(3 * time.Second) // 确保协程有足够的时间完成//高并发例子FindPrime()//单线程noGoroutine()
    }func FindPrime() {var num intvar n intn, _ = fmt.Scanf("%d", &num)fmt.Println(n, num)n = numif num > 32 { // 当num很大时,增大n(即goroutine的数量)却没有带来性能提升甚至导致耗时n = 32}// 对于num=1000000// n = 1, 即noGoroutine(),大约200ms~270ms// n = 2,大约150ms~250ms// n = 4,大约170ms~270ms// n = 8,大约>220ms// 对于num=10000000// n = 1, 即noGoroutine(),大约5s~6s// n = 2,大约2.7s~2.9s// n = 4,大约1.6s~2.0s// n = 8,大约1.8s~2s// n = 16(开始耗时增加), 大约2.1s~2.2s// 对于num=100000000// n = 1, 即noGoroutine(),>100s// n = 4,大约40s~41s// n = 8,大约25s~26s// n = 16, 大约22s~24s// n = 32,大约·32s~33svar primeChan = make(chan int, num)var inChan = make(chan int, num)var exitChan = make(chan bool, n)var start = time.Now()go inPrime(num, inChan)for i := 0; i < n; i++ {go primeJudge(inChan, primeChan, exitChan)}go func() {for i := 0; i < n; i++ {<-exitChan}close(primeChan)}()for {_, ok := <-primeChanif !ok {break}//fmt.Println(res)}close(exitChan)fmt.Println(time.Since(start))
    }func inPrime(num int, inChan chan int) {for i := 2; i < num; i++ {inChan <- i}close(inChan)
    }func primeJudge(inChan chan int, primeChan chan int, exitChan chan bool) {var flag boolfor {v, ok := <-inChan//fmt.Println("v = ", v, "; ok = ", ok)if !ok {break}flag = truefor i := 2; i < int(math.Sqrt(float64(v))); i++ {if v%i == 0 {flag = falsebreak}}if flag {primeChan <- v}}exitChan <- true
    }func noGoroutine() {var num intn, _ := fmt.Scanf("%d", &num)fmt.Println(n, num)var count = 0var start = time.Now()for i := 2; i <= num; i++ {var flag = truefor j := 2; j < (int)(math.Sqrt(float64(i))); j++ {if i%j == 0 {flag = falsebreak}if flag {count++}}}fmt.Println(time.Since(start))
    }
    

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

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

    相关文章

    Dubbo(30)如何配置Dubbo的服务分片?

    配置Dubbo的服务分片&#xff08;也称为服务分组&#xff09;可以帮助你将不同的服务实例分组&#xff0c;以实现隔离和管理。通过服务分片&#xff0c;可以在同一个注册中心中注册多个相同接口的服务&#xff0c;但它们属于不同的分组&#xff0c;消费者可以根据需要选择特定分…

    文档的预解析

    1. 预解析的核心目标 浏览器在正式解析&#xff08;Parsing&#xff09;HTML 前&#xff0c;会启动一个轻量级的 预解析器&#xff08;Pre-Parser&#xff09;&#xff0c;快速扫描文档内容&#xff0c;实现&#xff1a; 提前发现并加载关键资源&#xff08;如 CSS、JavaScrip…

    通过构造函数和几何条件,研究了不同函数的最近点存在性、性质及单调性

    解&#xff1a; &#xff08;1&#xff09;对于函数 f ( x ) 1 x f(x) \frac{1}{x} f(x)x1​ 和点 M ( 1 , 0 ) M(1, 0) M(1,0)&#xff0c;构造函数 s ( x ) ( x − 1 ) 2 ( 1 x ) 2 s(x) (x - 1)^2 \left(\frac{1}{x}\right)^2 s(x)(x−1)2(x1​)2。求导得到 s ′ …

    C语言之编译和debug工具

    gcc gcc是GUN项目为C和C提供的编译器 入门案例 gcc编译器最简单的使用案例&#xff1a;gcc hello.c -o hello&#xff0c;hello.c是源文件&#xff0c;-o参数指定了结果文件的名称 gcc命令的选项&#xff1a; -v&#xff1a;打印编译细节-E&#xff1a;仅仅进行预处理&…

    Altshuller矛盾矩阵查询:基于python和streamlit

    基于python和streamlit实现的Altshuller矛盾矩阵查询 import streamlit as st import json# 加载数据 st.cache_resource def load_data():with open(parameter.json, encodingutf-8) as f:parameters json.load(f)with open(way.json, encodingutf-8) as f:contradictions …

    Maven的下载配置及在Idea中的配置

    编写项目管理中存在的问题 在大型Java项目开发中&#xff0c;依赖管理是一个极其复杂的挑战。传统方式下&#xff0c;开发者需要手动下载并引入数十甚至上百个JAR包到项目中&#xff0c;这一过程不仅繁琐低效&#xff0c;还存在诸多痛点&#xff1a; 依赖传递性问题&#xff1a…

    来聊聊C++中的vector

    一.vector简介 vector是什么 C 中的 vector 是一种序列容器&#xff0c;它允许你在运行时动态地插入和删除元素。 vector 是基于数组的数据结构&#xff0c;但它可以自动管理内存&#xff0c;这意味着你不需要手动分配和释放内存。 与 C 数组相比&#xff0c;vector 具有更多的…

    WVP-GB28181摄像头管理平台存在弱口令

    免责声明&#xff1a;本号提供的网络安全信息仅供参考&#xff0c;不构成专业建议。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权&#xff0c;请及时与我联系&#xff0c;我将尽快处理并删除相关内容。 漏洞描述 攻击者可利用漏洞获取当前系统管…

    讯飞语音听写(流式版)开发指南

    语音交互大模型的功能越来越受到重视。讯飞语音听写&#xff08;流式版&#xff09;为开发者提供了一种高效、准确的语音识别解决方案。本文将基于 Home.vue、iat_xfyun.js 和 sparkChat.js 这三个文档&#xff0c;详细阐述讯飞语音听写&#xff08;流式版&#xff09;的开发逻…

    基于kotlin native的C与kotlin互相调用

    本文测试环境为ubuntu&#xff0c;没有使用IDE&#xff1b;从基本层面了解kotlin native环境中&#xff0c;C和kotlin的编译&#xff0c;互相调用。 1. kotlin 动态库 1.1 动态库编译 源码文件libktest.kt&#xff1a; //file name:libktest.kt OptIn(kotlin.experimental.…

    【教学类-102-02】自制剪纸图案(留白边、沿线剪)02——Python+PS自动化添加虚线边框

    背景需求: 01版本实现了对透明背景png图案边界线的扩展,黑线实线描边 【教学类-102-01】自制剪纸图案(留白边、沿线剪)01-CSDN博客文章浏览阅读974次,点赞15次,收藏7次。【教学类-102-01】自制剪纸图案(留白边、沿线剪)01https://blog.csdn.net/reasonsummer/article…

    Python-函数参数

    1. 参数基础 函数参数是向函数传递数据的主要方式&#xff0c;Python 提供了多种参数传递机制。 基本用法 def greet(name): # name 是形式参数print(f"Hello, {name}!")greet("Alice") # "Alice" 是实际参数使用场景&#xff1a;当函数需要…

    《在 Ubuntu 22.04 上安装 CUDA 11.8 和 Anaconda,并配置环境变量》

    安装 CUDA 11.8 和 Anaconda 并配置环境变量 在本教程中&#xff0c;我们将介绍如何在 Ubuntu 22.04 上安装 CUDA 11.8 和 Anaconda&#xff0c;并配置相应的环境变量。我们还将配置使用 阿里云镜像源 来加速软件包更新。以下是具体步骤。 步骤 1&#xff1a;更新软件源 首先…

    Ubuntu环境基于Ollama部署DeepSeek+Open-Webui实现本地部署大模型-无脑部署

    Ollama介绍 Ollama是一款简单好用的模型部署工具,不仅可以部署DeepSeek,市面上开源模型大部分都可以一键部署,这里以DeepSeek为例 官网 DeepSeek 版本硬件要求 安装Ollama 环境 sudo apt update sudo apt install curl sudo apt install lsof1.命令一键安装 在官网点击…

    Angular 项目 PDF 批注插件库在线版 API 示例教程

    本文章介绍 Angular 项目中 PDF 批注插件库 ElasticPDF 在线版 API 示例教程&#xff0c;API 包含 ① 导出批注后PDF数据&#xff1b;② 导出纯批注 json 数据&#xff1b;③ 加载旧批注&#xff1b;④ 切换文档&#xff1b;⑤ 切换用户&#xff1b;⑥ 清空批注 等数据处理功能…

    Spring Boot 中利用 Jasypt 实现数据库字段的透明加密解密

    1. 引言 1.1 什么是 Jasypt Jasypt(Java Simplified Encryption)是一个用于简化 Java 应用程序中加密操作的库。 1.2 为什么使用 Jasypt 简化加密操作:提供简单的 API 进行加密和解密。透明加密:自动处理加密和解密过程,无需手动干预。多种加密算法:支持多种加密算法,…

    Linux的: /proc/sys/net/ipv6/conf/ 笔记250405

    Linux的: /proc/sys/net/ipv6/conf/ /proc/sys/net/ipv6/conf/ 是 Linux 系统中用于 动态配置 IPv6 网络接口参数 的核心目录。它允许针对不同网络接口&#xff08;如 eth0、wlan0&#xff09;或全局设置&#xff08;all&#xff09;调整 IPv6 协议栈的行为。 它通过虚拟文件系…

    Spring Cloud 框架为什么能处理高并发

    Spring Cloud框架能够有效处理高并发场景&#xff0c;核心在于其微服务架构设计及多组件的协同作用&#xff0c;具体机制如下&#xff1a; 一、分布式架构设计支撑高扩展性 服务拆分与集群部署 Spring Cloud通过微服务拆分将单体系统解耦为独立子服务&#xff0c;每个服务可独…

    无人机智慧路灯杆:智慧城市的‘全能助手’

    在城市发展的进程中&#xff0c;智慧路灯杆作为智慧城市建设的关键载体&#xff0c;正逐步从传统的照明设备转型为集多种功能于一体的智能基础设施。无人机与智慧路灯杆的创新性融合&#xff0c;为城市管理和服务带来了全新的变革与机遇。 一、无人机智慧路灯杆的功能概述 照…

    Libevent UDP开发指南

    UDP 与 TCP 的核心区别 无连接:不需要建立/维护连接 不可靠:不保证数据包顺序和到达 高效:头部开销小,没有连接管理负担 支持广播/多播:可以向多个目标同时发送数据 一、基础UDP服务器实现 1. 创建 UDP 套接字 #include <event2/event.h> #include <event2/lis…