Golang的Goroutine(协程)与runtime

news2025/4/9 0:58:22

目录

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 main
    
    import (
        "fmt"
        "runtime"
    )
    
    func print() {
        fmt.Println("这是一个新的Goroutine。")
    }
    
    func main() {
        go print()  // 启动一个新的Goroutine
        fmt.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 main
    
    import (
        "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 main
    
    import (
        "fmt"
        "math"
        "runtime"
        "sync"
        "time"
    )
    
    func findPrime(num int, inChan chan int, primeChan chan int, exitChan chan bool) {
        var flag bool
        for {
            v, ok := <-inChan
            if !ok {
                break
            }
            flag = true
            for i := 2; i < int(math.Sqrt(float64(v))); i++ {
                if v % i == 0 {
                    flag = false
                    break
                }
            }
            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个Goroutine
    
        go 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 := 0
        for 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 main
    
    import (
    	"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 int
    	var n int
    	n, _ = fmt.Scanf("%d", &num)
    	fmt.Println(n, num)
    	n = num
    	if 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~33s
    
    	var 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 := <-primeChan
    		if !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 bool
    	for {
    		v, ok := <-inChan
    		//fmt.Println("v = ", v, "; ok = ", ok)
    		if !ok {
    			break
    		}
    		flag = true
    		for i := 2; i < int(math.Sqrt(float64(v))); i++ {
    			if v%i == 0 {
    				flag = false
    				break
    			}
    		}
    		if flag {
    			primeChan <- v
    		}
    	}
    	exitChan <- true
    }
    
    func noGoroutine() {
    	var num int
    	n, _ := fmt.Scanf("%d", &num)
    	fmt.Println(n, num)
    	var count = 0
    	var start = time.Now()
    	for i := 2; i <= num; i++ {
    		var flag = true
    		for j := 2; j < (int)(math.Sqrt(float64(i))); j++ {
    			if i%j == 0 {
    				flag = false
    				break
    			}
    			if flag {
    				count++
    			}
    		}
    	}
    	fmt.Println(time.Since(start))
    }
    

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

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

    相关文章

    C语言求3到100之间的素数

    一、代码展示 二、运行结果 三、感悟思考 注意: 这个题思路他是一个试除法的一个思路 先进入一个for循环 遍历3到100之间的数字 第二个for循环则是 判断他不是素数 那么就直接退出 这里用break 是素数就打印出来 在第一个for循环内 第二个for循环外

    【2025】物联网发展趋势介绍

    目录 物联网四层架构感知识别层网络构建层管理服务层——**边缘存储**边缘计算关键技术&#xff1a;综合应用层——信息应用 物联网四层架构 综合应用层&#xff1a;信息应用 利用获取的信息和知识&#xff0c;支持各类应用系统的运转 管理服务层&#xff1a;信息处理 对数据进…

    如何查看 MySQL 的磁盘空间使用情况:从表级到数据库级的分析

    在日常数据库管理中&#xff0c;了解每张表和每个数据库占用了多少磁盘空间是非常关键的。这不仅有助于我们监控数据增长&#xff0c;还能为性能优化提供依据。 Google Gemini中国版调用Google Gemini API&#xff0c;中国大陆优化&#xff0c;完全免费&#xff01;https://ge…

    汇编学习之《移位指令》

    这章节学习前需要回顾之前的标志寄存器的内容&#xff1a; 汇编学习之《标志寄存器》 算数移位指令 SAL (Shift Arithmetic Left)算数移位指令 : 左移一次&#xff0c;最低位用0补位&#xff0c;最高位放入EFL标志寄存器的CF位&#xff08;进位标志&#xff09; OllyDbg查看…

    Nature Communications上交、西湖大学、复旦大学研发面向机器人多模式运动的去电子化刚弹耦合高频自振荡驱动单元

    近年来&#xff0c;轻型仿生机器人因其卓越的运动灵活性与环境适应性受到国际机器人领域的广泛关注。然而&#xff0c;现有气动驱动器普遍受限于低模量粘弹性材料的回弹滞后效应与能量耗散特性&#xff0c;加之其"非刚即柔"的二元结构设计范式&#xff0c;难以同时满…

    对备忘录模式的理解

    对备忘录模式的理解 一、场景1、题目【[来源](https://kamacoder.com/problempage.php?pid1095)】1.1 题目描述1.2 输入描述1.3 输出描述1.4 输入示例1.5 输出示例 2、理解需求 二、不采用备忘录设计模式1、代码2、问题3、错误的备忘录模式 三、采用备忘录设计模式1、代码1.1 …

    【数据结构】图的基本概念

    图的定义 通俗来说一堆顶点被一堆线连在一起&#xff0c;这一坨顶点与线的集合 目录 图的定义 术语 有向图与无向图 简单图与多重图 度、入度与出度 路径与回路 路径长度与距离 子图 连通、连通图与连通分量 强连通、强连通图与强连通分量 完全图 生成树与生成森林 权…

    激光加工中平面倾斜度的矫正

    在激光加工中&#xff0c;加工平面的倾斜度矫正至关重要&#xff0c;直接影响加工精度和材料处理效果。以下是系统的矫正方法和步骤&#xff1a; 5. 验证与迭代 二次测量&#xff1a;加工后重新检测平面度&#xff0c;确认残余误差。 反馈优化&#xff1a;根据误差分布修正补偿…

    rdiff-backup备份

    目录 1. 服务器备份知识点 1.1 备份策略 1.2 备份步骤和宝塔面板简介 1.3 CentOS7重要目录 2. 备份工具 2.1 tar -g 备份演示 2. rsync 备份演示 3. rdiff-backup 备份演示 4. 差异和优缺点 3. rdiff-backup安装和使用 3.1 备份命令rdiff-backup 3.2 恢复命令--…

    PE结构(十五)系统调用与函数地址动态寻找

    双机调试 当需要分析一个程序时&#xff0c;这个程序一定是可以调试的&#xff0c;操作系统也不例外。在调试过程中下断点是很重要的 当我们对一个应用程序下断点时&#xff0c;应用程序是挂起的。但当我们对操作系统的内核程序下断点时&#xff0c;被挂起的不是内核程序而是…

    webrtc 本地运行的详细操作步骤 1

    前言 选修课的一个课程设计&#xff0c;需要我们本地运行这个开源项目&#xff0c;给我的压力非常大&#xff0c;因为确实不是很熟练这种操作。但是还是得做。谨以此文&#xff0c;纪念这个过程。 之前自己在 github 上面看到有代码仓库&#xff0c;但是比较复杂&#xff0c;在…

    kali——httrack

    目录 前言 使用教程 前言 HTTrack 是一款运行于 Kali Linux 系统中的开源网站镜像工具&#xff0c;它能将网站的页面、图片、链接等资源完整地下载到本地&#xff0c;构建出一个和原网站结构相似的离线副本。 使用教程 apt install httrack //安装httrack工具 httrac…

    【计算机网络】Linux配置SNAT/DNAT策略

    什么是NAT&#xff1f; NAT 全称是 Network Address Translation&#xff08;网络地址转换&#xff09;&#xff0c;是一个用来在多个设备共享一个公网 IP上网的技术。 NAT 的核心作用&#xff1a;将一个网络中的私有 IP 地址&#xff0c;转换为公网 IP 地址&#xff0c;从而…

    AI安全:构建负责任且可靠的系统

    AI已成为日常生活中无处不在的助力&#xff0c;随着AI系统能力和普及性的扩展&#xff0c;安全因素变得愈发重要。从基础模型构建者到采用AI解决方案的企业&#xff0c;整个AI生命周期中的所有相关方都必须共同承担责任。 为什么AI安全至关重要&#xff1f; 对于企业而言&…

    VUE+SPRINGBOOT+语音技术实现智能语音歌曲管理系统

    语音控制歌曲的播放、暂停、增删改查 <template><div class"Music-container"><div style"margin: 10px 0"><!--检索部分--><el-input style"width: 200px;" placeholder"请输入歌曲名称"v-model"sen…

    使用 SignalR 在 .NET Core 8 最小 API 中构建实时通知

    示例代码&#xff1a;https://download.csdn.net/download/hefeng_aspnet/90448094 介绍 构建实时应用程序已成为现代 Web 开发中必不可少的部分&#xff0c;尤其是对于通知、聊天系统和实时更新等功能。SignalR 是 ASP.NET 的一个强大库&#xff0c;可实现服务器端代码和客户…

    复古未来主义屏幕辉光像素化显示器反乌托邦效果PS(PSD)设计模板样机 Analog Retro-Futuristic Monitor Effect

    这款模拟复古未来主义显示器效果直接取材于 90 年代赛博朋克电影中的黑客巢穴&#xff0c;将粗糙的屏幕辉光和像素化的魅力强势回归。它精准地模仿了老式阴极射线管显示器&#xff0c;能将任何图像变成故障频出的监控画面或高风险的指挥中心用户界面。和……在一起 2 个完全可编…

    技术驱动革新,强力巨彩LED软模组助力创意显示

    随着LED显示技术的不断突破&#xff0c;LED软模组因其独特的柔性特质和个性化显示效果&#xff0c;正逐渐成为各类应用场景的新宠。强力巨彩软模组R3.0H系列具备独特的可塑造型能力与技术创新&#xff0c;为商业展示、数字艺术、建筑装饰等领域开辟全新视觉表达空间。    LED…

    Spark,HDFS概述

    HDFS组成构架&#xff1a; 注&#xff1a; NameNode&#xff08;nn&#xff09;&#xff1a;就是 Master&#xff0c;它是一个主管、管理者。 (1) 管理 HDFS 的名称空间&#xff1b; (2) 配置副本策略。记录某些文件应该保持几个副本&#xff1b; (3) 管理数据块&#xff08;…

    【数据结构】图论进阶:生成树、生成森林与权值网络的终极解析

    图的基本概念 导读一、图中的树与森林1.1 生成树与生成森林1.1.1 生成树1.1.2 生成森林1.1.3 生成树、生成森林与连通分量结点的关系边的关系 1.2 有向图中的树与森林1.2.1 有向树与有向森林1.2.2 生产有向树与生成有向森林1.2.3 有向树与生成有向树的区别1.2.4 有向森林与生成…