【8】深入理解 Go 语言中的协程-从基础到高级应用
文章目录
- 一、引言 🌟
- 二、协程基础概念 🧐
- (一)什么是协程
- (二)协程与线程、进程的区别
- 三、协程的创建与启动 🚀
- (一)使用 go 关键字创建协程
- (二)简单的协程示例代码
- 四、协程间通信 📡
- (一)通道(Channel)的概念与作用
- (二)通道的创建与使用
- (三)使用通道在协程间传递数据
- 五、协程的同步与互斥 🔒
- (一)互斥锁(Mutex)的使用场景
- (二)使用 WaitGroup 实现协程同步
- 六、协程的生命周期管理 🌱
- (一)如何优雅地结束协程
- (二)处理协程中的错误
- 七、协程的性能优势 💪
- (一)对比传统线程模型的性能提升
- (二)在高并发场景下的表现
- 八、实际应用案例 🛠️
- (一)Web 服务器中的协程应用
- (二)数据处理任务中的协程使用
一、引言 🌟
在当今的软件开发世界中,并发编程已经成为一项必不可少的技能,尤其是在处理高并发场景和大规模数据处理时。Go 语言作为一门强大的编程语言,其协程(Goroutines)机制是其并发编程的核心优势之一。协程在 Go 语言中的重要地位就如同魔法棒,让开发者能够轻松地编写出高效、简洁且并发性能卓越的程序。它允许我们同时处理多个任务,就像一个魔法师同时操控多个魔法咒语一样,极大地提高了程序的执行效率和资源利用率,是构建高性能应用程序的关键所在。
二、协程基础概念 🧐
(一)什么是协程
协程是 Go 语言中的轻量级线程,是 Go 运行时环境管理的并发执行单元。它们在 Go 程序中独立运行,并且由 Go 运行时调度器负责调度,而非操作系统。可以将协程看作是一个函数的执行过程,它可以与其他协程同时运行,而不会阻塞程序的主线程。协程的创建和销毁开销极小,因此我们可以创建成千上万个协程而无需担心资源耗尽,这是传统线程所无法比拟的。
想象一下,你正在举办一场盛大的音乐会,每个音乐家(协程)都可以在舞台上尽情演奏自己的乐器,而不需要等待其他音乐家演奏完毕。每个音乐家可以随时开始、暂停或结束自己的演奏,这就是协程在程序中的工作方式。
(二)协程与线程、进程的区别
进程:
- 进程是操作系统进行资源分配和调度的基本单位,拥有独立的内存空间、文件句柄等资源。启动一个进程会消耗大量的系统资源,包括内存和 CPU 时间。例如,启动一个新的进程可能需要分配新的内存页表、初始化进程控制块等,开销较大。可以用 🖥️ 图标来表示进程。
线程:
- 线程是进程的一部分,共享进程的资源,如内存空间。一个进程可以包含多个线程,它们可以并发执行,但操作系统对线程的调度开销仍然相对较大,尤其是在频繁创建和销毁线程时,因为涉及到内核态和用户态的切换。可以用 🔗 图标来表示线程。
协程:
- 协程是更轻量级的执行单元,运行在用户态,由 Go 运行时调度器调度。协程的栈空间非常小,通常只有几 KB,而线程的栈空间可能需要 MB 级别的内存。协程之间的切换由 Go 运行时管理,切换开销极小,这使得 Go 程序可以创建大量协程。可以用 🚀 图标来表示协程。
以下是一个简单的代码示例,展示了协程和线程在 Go 语言中的使用区别:
package mainimport ("fmt""sync""time"
)// 模拟一个长时间运行的任务
func longTask(id int) {for i := 0; i < 5; i++ {fmt.Printf("Task %d: %d\n", id, i)time.Sleep(100 * time.Millisecond)}
}func main() {// 线程的使用(使用 sync.WaitGroup 来等待多个线程完成)var wg sync.WaitGroupwg.Add(2)go func() {defer wg.Done()longTask(1)}()go func() {defer wg.Done()longTask(2)}()wg.Wait()// 协程的使用for i := 3; i <= 4; i++ {go longTask(i)}time.Sleep(1 * time.Second)
}
在上述代码中,我们使用 sync.WaitGroup 来等待两个使用 go 关键字创建的协程(模拟线程)完成,然后使用 go 关键字创建另外两个协程。可以看到,协程的创建和使用更加简洁,不需要额外的等待机制,因为它们的生命周期通常由程序逻辑控制。
三、协程的创建与启动 🚀
(一)使用 go 关键字创建协程
使用 go 关键字是创建协程最基本的方法。当我们在函数调用前添加 go 关键字时,Go 运行时会将该函数作为一个协程启动。例如:
package mainimport ("fmt""time"
)func printHello() {fmt.Println("Hello from Goroutine!")time.Sleep(1 * time.Second)
}func main() {// 创建并启动一个协程go printHello()fmt.Println("Hello from Main!")time.Sleep(2 * time.Second)
}
在这个示例中,go printHello() 这行代码创建并启动了一个协程,该协程会调用 printHello 函数。printHello 函数会打印一条消息并睡眠 1 秒。注意,main 函数中的 time.Sleep(2 * time.Second) 是为了防止程序在协程完成之前退出,因为一旦 main 函数结束,程序会终止,所有的协程也会随之终止。
(二)简单的协程示例代码
让我们来看一个更复杂的示例,同时启动多个协程:
package mainimport ("fmt""sync""time"
)func worker(id int, wg *sync.WaitGroup) {defer wg.Done()fmt.Printf("Worker %d starting\n", id)time.Sleep(time.Second)fmt.Printf("Worker %d done\n", id)
}func main() {var wg sync.WaitGroupfor i := 1; i <= 5; i++ {wg.Add(1)go worker(i, &wg)}wg.Wait()fmt.Println("All workers done")
}
在这个示例中:
sync.WaitGroup用于等待所有协程完成任务。可以用 ⏳ 图标表示等待。worker函数接收一个id和wg指针作为参数,defer wg.Done()确保在函数结束时通知WaitGroup该协程已完成任务。wg.Add(1)增加WaitGroup的计数,表示有一个新的协程正在运行。go worker(i, &wg)创建并启动协程。wg.Wait()会阻塞main函数,直到WaitGroup的计数为 0,即所有协程都完成任务。
四、协程间通信 📡
(一)通道(Channel)的概念与作用
通道是协程间通信的主要方式,它是一种类型安全的管道,用于在协程之间传递数据。通道可以保证数据的同步传递,避免了数据竞争和并发访问的问题。可以把通道想象成一个管道,数据通过这个管道从一个协程流向另一个协程,确保数据的有序和安全传递。可以用 ⛓️ 图标表示通道。
(二)通道的创建与使用
通道的创建使用 make 函数,有两种类型:无缓冲通道和有缓冲通道。
无缓冲通道:
ch := make(chan int)
无缓冲通道在发送和接收操作时必须同时进行,否则发送或接收操作会阻塞。
有缓冲通道:
ch := make(chan int, 3)
有缓冲通道可以存储一定数量的数据,发送操作在缓冲区未满时不会阻塞,接收操作在缓冲区不为空时不会阻塞。
以下是一个简单的代码示例:
package mainimport ("fmt""time"
)func main() {ch := make(chan int)go func() {fmt.Println("Sending data...")ch <- 42 // 发送数据到通道fmt.Println("Data sent")}()time.Sleep(1 * time.Second)fmt.Println("Receiving data...")data := <-ch // 从通道接收数据fmt.Println("Received data:", data)
}
在这个示例中,一个协程向通道发送数据,而 main 协程从通道接收数据。由于通道是无缓冲的,发送操作会阻塞,直到接收操作发生。
(三)使用通道在协程间传递数据
以下是一个更复杂的示例,展示如何使用通道在多个协程间传递数据:
package mainimport ("fmt""sync"
)func producer(ch chan<- int, wg *sync.WaitGroup) {defer wg.Done()for i := 0; i < 5; i++ {ch <- i}close(ch)
}func consumer(ch <-chan int, wg *sync.WaitGroup) {defer wg.Done()for num := range ch {fmt.Println("Received:", num)}
}func main() {var wg sync.WaitGroupch := make(chan int)wg.Add(2)go producer(ch, &wg)go consumer(ch, &wg)wg.Wait()fmt.Println("All done")
}
在这个示例中:
producer函数将数据发送到通道,并在发送完数据后关闭通道。consumer函数使用for...range从通道接收数据,当通道关闭时,for...range会自动结束。chan<- int表示只发送通道,<-chan int表示只接收通道,这保证了数据只能单向流动,增强了代码的安全性。
五、协程的同步与互斥 🔒
(一)互斥锁(Mutex)的使用场景
互斥锁用于保护共享资源,防止多个协程同时访问共享数据,避免数据竞争。例如,当多个协程同时访问和修改一个全局变量时,可能会导致不可预期的结果,使用互斥锁可以确保同一时间只有一个协程可以访问该变量。可以用 🔐 图标表示互斥锁。
以下是一个使用互斥锁的示例:
package mainimport ("fmt""sync""time"
)var (counter intmu sync.Mutex
)func increment(wg *sync.WaitGroup) {defer wg.Done()mu.Lock()counter++mu.Unlock()
}func main() {var wg sync.WaitGroupfor i := 0; i < 1000; i++ {wg.Add(1)go increment(&wg)}wg.Wait()fmt.Println("Counter value:", counter)
}
在这个示例中:
mu.Lock()用于锁定共享资源,mu.Unlock()用于解锁。counter是一个全局变量,多个协程通过increment函数对其进行加 1 操作。- 互斥锁确保每次只有一个协程能修改
counter,避免了数据竞争。
(二)使用 WaitGroup 实现协程同步
我们已经在之前的示例中使用过 sync.WaitGroup,它是一种同步机制,用于等待一组协程完成任务。Add 方法增加等待组的计数,Done 方法减少计数,Wait 方法阻塞直到计数为 0。可以用 👥 图标表示等待组。
以下是另一个使用 WaitGroup 的示例,展示如何等待多个协程完成不同的任务:
package mainimport ("fmt""sync""time"
)func worker(id int, wg *sync.WaitGroup) {defer wg.Done()fmt.Printf("Worker %d starting\n", id)time.Sleep(time.Duration(id) * time.Second)fmt.Printf("Worker %d done\n", id)
}func main() {var wg sync.WaitGroupfor i := 1; i <= 5; i++ {wg.Add(1)go worker(i, &wg)}wg.Wait()fmt.Println("All workers done")
}
在这个示例中,每个 worker 协程会睡眠一段时间,模拟不同的任务时间,WaitGroup 确保 main 函数等待所有协程完成后才继续执行。
六、协程的生命周期管理 🌱
(一)如何优雅地结束协程
协程的生命周期通常由其函数的执行结束或程序终止而结束。但有时我们需要提前终止协程,一种方法是使用通道来发送终止信号。
以下是一个示例:
package mainimport ("fmt""time"
)func worker(done chan bool) {for {select {case <-done:fmt.Println("Worker stopping")returndefault:fmt.Println("Worker running")time.Sleep(1 * time.Second)}}
}func main() {done := make(chan bool)go worker(done)time.Sleep(5 * time.Second)done <- truetime.Sleep(1 * time.Second)fmt.Println("Main done")
}
在这个示例中:
worker协程使用select语句监听done通道。- 当
done通道接收到信号时,协程会退出。
(二)处理协程中的错误
在协程中处理错误非常重要,一种常见的方法是使用通道来传递错误信息。
以下是一个处理协程错误的示例:
package mainimport ("fmt""sync"
)func worker(id int, errCh chan<- error) {defer func() {if r := recover(); r!= nil {errCh <- fmt.Errorf("Worker %d panicked: %v", id, r)}}()if id == 2 {panic("Something went wrong in worker 2")}
}func main() {var wg sync.WaitGrouperrCh := make(chan error)for i := 1; i <= 3; i++ {wg.Add(1)go func(id int) {defer wg.Done()worker(id, errCh)}(i)}go func() {wg.Wait()close(errCh)}()for err := range errCh {if err!= nil {fmt.Println(err)}}
}
在这个示例中:
worker函数使用recover来捕获panic并将错误发送到errCh通道。main函数使用for...range从errCh接收错误信息并处理。
七、协程的性能优势 💪
(一)对比传统线程模型的性能提升
传统的线程模型在创建和切换时需要操作系统的介入,开销较大。而 Go 语言的协程由 Go 运行时管理,创建和切换的开销极小。以下是一个简单的性能测试:
package mainimport ("fmt""sync""time"
)func threadTask() {time.Sleep(10 * time.Millisecond)
}func goroutineTask() {time.Sleep(10 * time.Millisecond)
}func main() {start := time.Now()var wg sync.WaitGroupfor i := 0; i < 1000; i++ {wg.Add(1)go func() {defer wg.Done()threadTask()}()}wg.Wait()threadTime := time.Since(start)start = time.Now()for i := 0; i < 1000; i++ {wg.Add(1)go goroutineTask()}wg.Wait()goroutineTime := time.Since(start)fmt.Printf("Thread time: %v\nGoroutine time: %v\n", threadTime, goroutineTime)
}
这个示例通过创建 1000 个线程和 1000 个协程执行相同的任务并睡眠,对比它们的执行时间,可以发现协程的性能优势。
(二)在高并发场景下的表现
在高并发场景下,如 Web 服务器或数据处理服务,协程的性能优势更加明显。由于可以创建大量的协程而无需过多的资源开销,Go 语言可以轻松处理数以万计的并发连接。例如,一个简单的 HTTP 服务器可以使用协程来处理每个请求,而不会因为大量的并发连接而导致性能下降。可以用 🌐 图标表示高并发场景。
以下是一个简单的 HTTP 服务器示例:
package mainimport ("fmt""net/http"
)func handler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello, World!")
}func main() {http.HandleFunc("/", handler)fmt.Println("Starting server at :8080")if err := http.ListenAndServe(":8080", nil); err!= nil {fmt.Println("Server failed:", err)}
}
在这个示例中,Go 的 HTTP 服务器会为每个请求创建一个协程来处理,而无需手动管理线程和连接池,充分发挥了协程的优势。
八、实际应用案例 🛠️
(一)Web 服务器中的协程应用
以下是一个更复杂的 Web 服务器示例,展示如何使用协程处理不同的请求:
package mainimport ("fmt""net/http""sync""time"
)func handleRequest(w http.ResponseWriter, r *http.Request, wg *sync.WaitGroup) {defer wg.Done()fmt.Printf("Handling request from %s\n", r.RemoteAddr)time.Sleep(1 * time.Second)fmt.Fprintf(w, "Request handled by %s\n", r.RemoteAddr)
}func main() {var wg sync.WaitGrouphttp.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {wg.Add(1)go handleRequest(w, r, &wg)})fmt.Println("Starting server at :8080")if err := http.ListenAndServe(":8080", nil); err!= nil {fmt.Println("Server failed:", err)}
}
在这个示例中,handleRequest 函数会在协程中处理每个请求,使用 sync.WaitGroup 确保请求得到正确处理。
(二)数据处理任务中的协程使用
假设我们需要处理大量的数据,例如处理一个大文件中的数据行:
package mainimport ("bufio""fmt""os""sync"
)func processLine(line string, wg *sync.WaitGroup, resultCh chan<- string) {defer wg.Done()// 这里可以进行数据处理,如解析、转换等操作resultCh <- "Processed: " + line
}func main() {file, err := os.Open("large_file.txt")if err!= nil {fmt.Println("Error opening file:", err)return}defer file.Close()var wg sync.WaitGroupresultCh := make(chan string)scanner := bufio.NewScanner(file)for scanner.Scan() {wg.Add(1)go processLine(scanner.Text(), &wg, resultCh)}go func() {wg.Wait()close(resultCh)}()for result := range resultCh {fmt.Println(result)}
}
在这个示例中:
processLine函数处理文件中的每一行数据,使用协程并发处理。sync.WaitGroup确保所有行都被处理完。- 处理结果通过
resultCh通道传递和接收。
相关文章:
【8】深入理解 Go 语言中的协程-从基础到高级应用
文章目录 一、引言 🌟二、协程基础概念 🧐(一)什么是协程(二)协程与线程、进程的区别三、协程的创建与启动 🚀(一)使用 go 关键字创建协程(二)简单…...
深入理解 ECMAScript 2024 新特性:字符串 isWellFormed 方法
ECMAScript 2024 引入了一个新的字符串实例方法:String.prototype.isWellFormed。这一新增功能是为了帮助开发者更容易地验证字符串是否为有效的 Unicode 文本。本文将详细介绍这一方法的使用场景、实现原理及其在实际应用中的价值。 String.prototype.isWellFormed…...
算法分析与设计之贪心算法
文章目录 前言一、Greedy Algorithms1.1 贪心选择性质1.2 最优子结构性质1.3 正确性证明 二、典型例题2.1 Interval Scheduling间隔调度2.2 Interval Partitioning最少间教室排课2.3 Selecting Breakpoints选择加油站停靠点2.4 硬币找零2.5 Scheduling to Minimizing Lateness2…...
Centos 宝塔安装
yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh 安装成功界面 宝塔说明文档 https://www.bt.cn/admin/servers#wcu 或者可以注册宝塔账号 1 快速部署 安装docker 之后 2 需要在usr/bin下下载do…...
蓝桥与力扣刷题(709 转换成小写字母)
题目:给你一个字符串 s ,将该字符串中的大写字母转换成相同的小写字母,返回新的字符串。 示例 1: 输入:s "Hello" 输出:"hello"示例 2: 输入:s "here…...
Redis的过期策略、内存淘汰机制
Redis只能存5G数据,可是你写了10G,那会删5G的数据。怎么删的?还有,你的数据已经设置了过期时间,但是时间到了,为什么内存占用率还是比较高? 一、Redis的过期策略 Redis采用的是定期删除惰性删除策略。 1…...
视觉多模态大模型---MiniMax-vl-01---以闪电般的注意力缩放基础模型
简介 MiniMax-VL-01 是与今年1月15日由上海稀宇科技有限公司(MiniMax)发布并开源的一款视觉多模态大模型,它与基础语言大模型 MiniMax-Text-01 一同构成了 MiniMax-01 系列。这款模型的设计初衷是为了应对日益增长的长上下文处理需求&#x…...
【微服务】面试 3、 服务监控 SkyWalking
微服务监控的原因 问题定位:在微服务架构中,客户端(如 PC 端、APP 端、小程序等)请求后台服务需经过网关再路由到各个微服务,服务间可能存在多链路调用。当某一微服务挂掉时,在复杂的调用链路中难以迅速确定…...
【案例81】NMC调用导致数据库的效率问题
问题现象 客户在使用NC系统时,发现系统特别卡顿。需要紧急排查。 问题分析 排查NMC发现,所有的线程都处于执行SQL层面,说明数据库当前出现了异常。查看数据库资源状态发现,Oracle相关进程CPU利用率达到了100%。 查看现在数据库…...
Linux_信号
信号的概念 && 知识补充 信号是进程之间事件异步通知的一种方式,是一种软中断。 标准信号:编号为1-31之间都是标准信号,这些都是预定义信号,用于通知进程发生的各种事件。实时信号:编号从32开始起均是实时信号…...
LeetCode100之搜索二维矩阵(46)--Java
1.问题描述 给你一个满足下述两条属性的 m x n 整数矩阵: 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回…...
学员答疑:安卓分屏窗口的TouchableRegion设置流程追踪
背景: vip学员在群里问到了一个分屏触摸区域设置的问题,开始以为就是和普通Activity设置区域没啥差别,都是在InputMonitor中进行的设置,但是仔细研究下来其实并不是哈。本文就带大家来手把手分析一下分屏情况下的触摸区域是怎么设置的。 d…...
[cg] UE5 调试技巧
UE 中 rhi命令的提交是在render 线程,而graphics api 真正的执行是在rhi 线程, 今天想看下rhi的底层调用,但由于是通过task执行的,无法获取到render thread传入的地方,调试起来不太方便。 可通过开启下面的命令来调试 …...
Python Wi-Fi密码测试工具
Python Wi-Fi测试工具 相关资源文件已经打包成EXE文件,可双击直接运行程序,且文章末尾已附上相关源码,以供大家学习交流,博主主页还有更多Python相关程序案例,秉着开源精神的想法,望大家喜欢,点…...
Linux 创建用户
Linux 创建用户 创建用户 sudo useradd -m -s /bin/bash test - -m:自动创建家目录 /home/test - -s /bin/bash:指定默认的 shell 为 bash修改密码 # 修改密码 sudo passwd test删除用户 userdel -r zengshun - -r:把用户的主目录一起删…...
自建RustDesk服务器
RustDesk服务端 下面的截图是我本地的一个服务器做为演示用,你自行的搭建服务需要该服务器有固定的ip地址 1、通过宝塔面板快速安装 2、点击【安装】后会有一个配置信息,默认即可 3、点击【确认】后会自动安装等待安装完成 4、安装完成后点击【打开…...
Spring Boot Web技术栈(官网文档解读)
摘要 Spring Boot框架既支持传统的Servlet技术栈,也支持新兴的响应式(Reactive)技术栈。本篇文章将详细讲述Spring Boot 对两种技术栈的详细支持和使用。 Servlet 概述 基于Java Servlet API构建,它依赖于传统的阻塞I/O模型&…...
【llama_factory】qwen2_vl训练与批量推理
训练llama factory配置文件 文件:examples/train_lora/qwen2vl_lora_sft.yaml ### model model_name_or_path: qwen2_vl/model_72b trust_remote_code: true### method stage: sft do_train: true finetuning_type: lora lora_target: all### dataset dataset: ca…...
wpa_cli命令使用记录
wpa_cli可以用于查询当前状态、更改配置、触发事件和请求交互式用户输入。具体来说,它可以显示当前的认证状态、选择的安全模式、dot11和dot1x MIB等,并可以配置一些变量,如EAPOL状态机参数。此外,wpa_cli还可以触发重新关联和IEE…...
【Uniapp-Vue3】页面生命周期onLoad和onReady
一、onLoad函数 onLoad在页面载入时触发,多用于页面跳转时进行参数传递。 我们在跳转的时候传递参数name和age: 接受参数: import {onLoad} from "dcloudio/uni-app"; onLoad((e)>{...}) 二、onReady函数 页面生命周期函数中的onReady其…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...
Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...
