并发编程模型

线程模型:Go的Goroutine
  • Goroutine(M:N 模型)

    package mainimport ("fmt""runtime""sync""time"
    )func main() {// 查看当前机器的逻辑CPU核心数,决定Go运行时使用多少OS线程fmt.Println("CPU Cores:", runtime.NumCPU())// 启动一个Goroutine:只需一个 `go` 关键字go func() {fmt.Println("I'm running in a goroutine!")}()// 启动10万个Goroutine轻而易举var wg sync.WaitGroup // 用于等待Goroutine完成for i := 0; i < 100000; i++ {wg.Add(1)go func(taskId int) {defer wg.Done() // 任务完成时通知WaitGroup// 模拟一些工作,比如等待IOtime.Sleep(100 * time.Millisecond)fmt.Printf("Task %d executed.\n", taskId)}(i)}wg.Wait() // 等待所有Goroutine结束
    }
    
  • 极轻量

    • 内存开销极小:初始栈大小仅2KB,并且可以按需动态扩缩容。创建100万个Goroutine也只需要大约2GB内存(主要开销是堆内存),而100万个Java线程需要TB级内存。
    • 创建和销毁开销极低:由Go运行时在用户空间管理,不需要系统调用,只是分配一点内存,速度极快(比Java线程快几个数量级)。
  • M:N 调度模型:这是Go高并发的魔法核心。

    • Go运行时创建一个少量的OS线程(默认为CPU核心数,如4核机器就创建4个)。
    • 成千上万的Goroutine被多路复用在这少量的OS线程上。
    • Go运行时自身实现了一个工作窃取(Work-Stealing) 的调度器,负责在OS线程上调度Goroutine。
  • 智能阻塞处理:当一个Goroutine执行阻塞操作(如I/O)时,Go调度器会立即感知到

    • 它会迅速将被阻塞的Goroutine从OS线程上移走。
    • 然后在该OS线程上调度另一个可运行的Goroutine继续执行。
    • 这样,OS线程永远不会空闲,始终保持在忙碌状态。阻塞操作完成后,相应的Goroutine会被重新放回队列等待执行。
通信机制:Go的CSP模型:Channel通信
  • 语法和结构

    package mainimport ("fmt""time"
    )func producer(ch chan<- string) { // 参数:只写Channelch <- "Data" // 1. 发送数据到Channel(通信)fmt.Println("Produced and sent data")
    }func consumer(ch <-chan string) { // 参数:只读Channeldata := <-ch // 2. 从Channel接收数据(通信)// 一旦收到数据,说明“内存(数据)”的所有权从producer转移给了consumerfmt.Println("Consumed:", data)
    }func main() {// 创建一个Channel(通信的管道),类型为stringmessageChannel := make(chan string)// 启动生产者Goroutine和消费者Goroutine// 它们之间不共享内存,只共享一个Channel(用于通信)go producer(messageChannel)go consumer(messageChannel)// 给Goroutine一点时间执行time.Sleep(100 * time.Millisecond)// 更复杂的例子:带缓冲的ChannelbufferedChannel := make(chan int, 2) // 缓冲大小为2bufferedChannel <- 1                 // 发送数据,不会阻塞,因为缓冲未满bufferedChannel <- 2// bufferedChannel <- 3               // 这里会阻塞,因为缓冲已满,直到有接收者拿走数据fmt.Println(<-bufferedChannel) // 接收数据fmt.Println(<-bufferedChannel)// 使用Range和Closego func() {for i := 0; i < 3; i++ {bufferedChannel <- i}close(bufferedChannel) // 发送者关闭Channel,表示没有更多数据了}()// 接收者可以用for-range循环自动接收,直到Channel被关闭for num := range bufferedChannel {fmt.Println("Received:", num)}
    }
    
  • 核心:Goroutine 是被动的,它们通过 Channel 发送和接收数据来进行协作。通信同步了内存的访问

  • Channel 的行为

    • 同步:无缓冲 Channel 的发送和接收操作会阻塞,直到另一边准备好。这天然地同步了两个 Goroutine 的执行节奏。
    • 所有权转移:当数据通过 Channel 发送后,可以认为发送方“放弃”了数据的所有权,接收方“获得”了它。这避免了双方同时操作同一份数据。
  • 优点

    • 清晰易懂:数据流清晰可见。并发逻辑由 Channel 的连接方式定义,而不是由错综复杂的锁保护区域定义。
    • 天生安全:从根本上避免了由于同时访问共享变量而引发的数据竞争问题。
    • 简化并发:开发者不再需要费心识别临界区和手动管理锁,大大降低了心智负担和出错概率。
  • Go 也提供了传统的锁sync.MutexChannel 并非万能。Go 的理念是:

    • 使用 Channel 来传递数据、协调流程
    • 使用 Mutex 来保护小范围的、简单的状态(例如,保护一个结构体内的几个字段)。
同步原语:sync.Mutex、WaitGroup
  • sync.Mutex(互斥锁)

    package mainimport ("fmt""sync"
    )type Counter struct {mu    sync.Mutex // 通常将Mutex嵌入到需要保护的数据结构中count int
    }func (c *Counter) Increment() {c.mu.Lock()         // 获取锁defer c.mu.Unlock() // 使用defer确保函数返回时一定会释放锁c.count++           // 临界区
    }
    
    • 显式操作:类似Java的Lock,需要手动调用Lock()Unlock()
    • defer是关键:Go社区强烈推荐使用defer mutex.Unlock()来确保锁一定会被释放,这比Java的try-finally模式更简洁,不易出错。
    • 不可重入:Go的Mutex是不可重入的。如果一个Goroutine已经持有一个锁,再次尝试获取同一个锁会导致死锁
  • sync.WaitGroup(等待组)

    func main() {var wg sync.WaitGroup // 创建一个WaitGroupurls := []string{"url1", "url2", "url3"}for _, url := range urls {wg.Add(1) // 每启动一个Goroutine,计数器+1go func(u string) {defer wg.Done() // Goroutine完成时,计数器-1(defer保证一定会执行)// 模拟抓取网页fmt.Println("Fetching", u)}(url)}wg.Wait() // 阻塞,直到计数器归零(所有Goroutine都调用了Done())fmt.Println("All goroutines finished.")
    }
    
    • WaitGroup更简洁:它的API(Add, Done, Wait)专为等待Goroutine组而设计,意图更明确,用法更简单。
    • 无需线程池WaitGroup直接与轻量的Goroutine配合,而Java通常需要与笨重的线程池(ExecutorService)一起使用。
深度对比:Goroutine与Java线程的轻量级特性
  • 用户态线程 vs. 内核态线程

    • Java线程1:1 模型的内核态线程,一个Java线程直接对应一个操作系统线程,由操作系统内核进行调度和管理。
    • GoroutineM:N 模型的用户态线程,成千上万个Goroutine被多路复用在少量操作系统线程上,在用户空间进行调度和管理。
  • 内存开销:Goroutine的内存效率比Java线程高出两个数量级,这使得在普通硬件上运行数十万甚至上百万的并发任务成为可能。

  • 创建与销毁:Goroutine的创建和销毁开销极低,这使得开发者可以采用更直观的Goroutine模式,无需纠结于复杂的池化技术。

  • 调度:Go调度器的用户态、协作式、工作窃取设计,使得它在高并发场景下的调度效率远高于OS内核调度器。

  • 阻塞处理:Go在语言运行时层面完美处理了阻塞问题,而Java需要在应用层通过复杂的非阻塞I/O库来规避此问题。

高级特性与元编程

泛型:Go的[T any](引入较晚,对比其应用场景)
  • 语法和结构

    // 1. 类型参数(Type Parameters)声明:使用方括号 []
    //    `[T any]` 表示一个类型参数T,其约束为`any`(即没有任何约束,可以是任何类型)
    func PrintSlice[T any](s []T) { // 泛型函数for _, v := range s {fmt.Println(v)}
    }// 2. 自定义约束(Constraints):使用接口定义类型集
    //    约束不仅可以要求方法,还可以要求底层类型(~int)或类型列表
    type Number interface {~int | ~int64 | ~float64 // 类型约束:只能是int、int64或float64(包括自定义衍生类型)
    }func Sum[T Number](s []T) T {var sum Tfor _, v := range s {sum += v}return sum
    }// 3. 泛型类型
    type MyStack[T any] struct {elements []T
    }func (s *MyStack[T]) Push(element T) {s.elements = append(s.elements, element)
    }func (s *MyStack[T]) Pop() T {element := s.elements[len(s.elements)-1]s.elements = s.elements[:len(s.elements)-1]return element
    }
    
  • 优点

    • 运行时类型安全:没有类似Java的“原始类型”概念,无法绕过类型检查。
    • 支持基本类型Sum([]int{1, 2, 3}) 可以直接工作,无装箱开销。
    • 更强大的约束:可以通过接口约束类型集(~int | ~float64),这是Java做不到的。
  • 缺点与限制(目前)

    • 语法略显冗长[T any] 相比 <T> 更占空间,尤其是多个参数时:[K comparable, V any]
    • 生态系统仍在适应:标准库和第三方库对泛型的应用是渐进的,不像Java那样无处不在。
反射:Java的Reflection vs Go的reflect
  • 语法和结构

    package mainimport ("fmt""reflect"
    )type Person struct {Name string `json:"name"` // 结构体标签(Tag)Age  int    `json:"age"`
    }func (p Person) Greet() {fmt.Printf("Hello, my name is %s\n", p.Name)
    }func main() {// 1. 获取Type和Value(反射的两个核心入口)p := Person{Name: "Alice", Age: 30}t := reflect.TypeOf(p)   // 获取类型信息 (reflect.Type)v := reflect.ValueOf(p)  // 获取值信息 (reflect.Value)fmt.Println("Type:", t.Name()) // Output: Personfmt.Println("Kind:", t.Kind()) // Output: struct (Kind是底层分类)// 2. 检查结构信息// - 检查结构体字段for i := 0; i < t.NumField(); i++ {field := t.Field(i)tag := field.Tag.Get("json") // 获取结构体标签fmt.Printf("Field %d: Name=%s, Type=%v, JSON Tag='%s'\n",i, field.Name, field.Type, tag)}// - 检查方法for i := 0; i < t.NumMethod(); i++ {method := t.Method(i)fmt.Printf("Method %d: %s\n", i, method.Name)}// 3. 动态操作// - 修改值(必须传入指针,且值必须是“可设置的”(Settable))pValue := reflect.ValueOf(&p).Elem() // 获取可寻址的Value (Elem()解引用指针)nameField := pValue.FieldByName("Name")if nameField.IsValid() && nameField.CanSet() {nameField.SetString("Bob") // 修改字段值}fmt.Println("Modified person:", p) // Output: {Bob 30}// - 调用方法greetMethod := v.MethodByName("Greet")if greetMethod.IsValid() {greetMethod.Call(nil) // 调用方法,无参数则传nil// 输出: Hello, my name is Alice (注意:v是基于原始p的Value,名字还是Alice)}// 4. 创建新实例var newPPtr interface{} = reflect.New(t).Interface() // reflect.New(t) 创建 *PersonnewP := newPPtr.(*Person)newP.Name = "Charlie"fmt.Println("Newly created person:", *newP) // Output: {Charlie 0}
    }
    
  • 显式且谨慎:API设计清晰地分离了TypeValue,修改值需要满足“可设置性”的条件,这是一种安全机制。

  • 功能侧重不同

    • 强项:对结构体(Struct) 的解析能力极强,是encoding/json等标准库的基石,结构体标签(Tag) 是其特色功能。
    • 弱项:无法访问未导出的成员(小写开头的字段/方法),这是Go反射一个非常重要的安全设计,它维护了包的封装性。
  • Kind 的概念:这是Go反射的核心,Kind表示值的底层类型(如reflect.Struct, reflect.Slice, reflect.Int),而Type是具体的静态类型,操作前常需要检查Kind

  • 性能开销:同样有较大开销,应避免在性能关键路径中使用。

  • 类型安全:比Java稍好,但Call()等方法依然返回[]reflect.Value,需要手动处理。

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

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

相关文章

弧形导轨如何提升新能源汽车的能效和续航里程?

弧形导轨在新能源汽车中的应用主要集中在电池生产线和自动化装配线等领域&#xff0c;通过提高生产效率和精度&#xff0c;间接提升新能源汽车的能效和续航里程。高精度装配&#xff1a;在新能源汽车的电池生产线中&#xff0c;弧形导轨用于高精度的自动化装配设备&#xff0c;…

考研择校考虑因素和备考流程

考研择校一、选择专业二、选择学校三、考研计划安排一、选择专业 1、了解自己的未来工作规划&#xff08;这里肯定没有啥规划&#xff09;&#xff1b; 2、连接考研的相关几个专业哪个好就业&#xff08;公务员和找工作&#xff09;&#xff1b; 3、知乎、小红书、deepseek都可…

1.13 Memory Profiler Package - Unity Objects(unity对象页签)

1.Unity Objects(Unity对象页签)简介 2.界面功能参数1.Unity Objects(Unity对象页签)简介 Unity Objects用于快速定位unity对象内存占用的类型和具体实例a.查找内存占用最大的资源, 判断这些资源是否可以压缩或延迟加载b.查找重复加载的资源c.查看运行时创建但是没有释放的资源…

Android真机-安装Reqable证书-抓SSL包

使用Reqable的自动安装系统证书无法正常抓包&#xff0c;所以就有了这篇文章超简单的安装方式 - 记得确保手机已拥有root权限一、从Reqable导出公钥证书无需使用OpenSSL 将 .pem 文件转换为 .0 格式注意是 .0 格式的这个证书二、推送证书到手机adb root adb remount adb push 证…

[超表面论文快讯-242] PR-微波超四元数涡旋阵列洛书加权锁定成像加密-江南大学王继成、上海科技大学王雄团队

栏目介绍&#xff1a; “论文快讯”栏目旨在精简地分享一周内发表在高水平期刊上的Metasurface领域研究成果&#xff0c;帮助读者及时了解领域前沿动态&#xff0c;如果对专栏的写法或内容有什么建议欢迎留言&#xff0c;后续会陆续开启其他专栏&#xff0c;敬请期待。 论文基…

案例研究:构建一个 Markdown 编辑器

引言&#xff1a;Markdown 编辑器案例在 Electron Node.js 开发中的研究价值与必要性 在 Electron 框架的实际项目应用中&#xff0c;构建一个 Markdown 编辑器是展示其强大能力的经典案例研究。它不仅仅是一个简单的文本工具&#xff0c;更是开发者通过完整项目演示 Electron…

十四十五. 图论

树与图的存储 树是一种特殊的图,与图的存储方式相同。 对于无向图中的边ab,存储两条有向边a->b, b->a。 因此我们可以只考虑有向图的存储。 (1) 邻接矩阵:g[a][b] 存储边a->b (2) 邻接表: // 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单…

内存管理这一块

文章目录前言一、C/C内存分布二、C语言中动态内存管理方式三.C的内存管理方式new/delete操作内置类型new/delete操作自定义类型四.定位new总结前言 在一行一行的代码之中&#xff0c;不同的数据存放的位置是有所不同的&#xff0c;正是因为这些数据的性质不同&#xff0c;所以…

linux 环境下Docker 安装

Docker在线安装 参考 &#xff1a;https://juejin.cn/book/6844733746462064654/section/6844733746545950734#heading-0 Ubuntu 环境下安装 $ apt-get install apt-transport-https ca-certificates curl software-properties-common $ install -m 0755 -d /etc/apt/keyrin…

Netty从0到1系列之Netty启动细节分析

文章目录一、Netty服务器端启动细节分析1.1 实现一个简单的http服务器1.2 服务器端启动细节分析1.3 创建与初始化 NioServerSocketChannel1.3.1 **通过反射工厂创建 Channel**&#xff1a;1.3.2 **初始化 Channel**1.4 注册到 Boss EventLoopGroup1.4.1 **异步提交注册任务**1.…

一个海康相机OCR的程序

这是一个极其复杂和庞大的​​机器视觉检测程序​​&#xff0c;其核心特点是​​多重冗余、条件判断和流程分支​​。它并非一个简单的线性流程&#xff0c;而是一个为应对各种复杂工业场景&#xff08;如光照变化、产品位置偏移、识别难度高等&#xff09;而设计的​​决策网…

深入解析:preload与prefetch的区别及最佳实践

在前端性能优化领域&#xff0c;资源加载策略直接影响页面的加载速度和用户体验。<link>标签的preload和prefetch属性是浏览器提供的两种关键资源预加载机制&#xff0c;它们都能提前加载资源&#xff0c;但适用场景和行为逻辑却大不相同。本文将从定义、触发时机、优先级…

[论文阅读] 人工智能 + 软件工程(漏洞检测)| 工业场景漏洞检测新突破:CodeBERT跨领域泛化能力评估与AI-DO工具开发

工业场景漏洞检测新突破&#xff1a;CodeBERT跨领域泛化能力评估与AI-DO工具开发 论文信息 论文原标题&#xff1a;Cross-Domain Evaluation of Transformer-Based Vulnerability Detection: Open-Source vs. Industrial Data引文格式&#xff08;APA&#xff09;&#xff1a;[…

【层面一】C#语言基础和核心语法-01(类型系统/面向对象/异常处理)

文章目录1 类型系统1.1 为什么需要类型&#xff1f;1.2 .NET 类型系统的两大支柱&#xff1a;CTS 和 CLS1.3 最根本的分类&#xff1a;值类型 vs 引用类型1.4 内置类型 vs. 自定义类型1.5 类型转换1.6 通用基类&#xff1a;System.Object2 面向对象编程2.1 类和对象2.2 接口和类…

Deepseek构建本地知识库

一.本地部署Deepseek Ollama 介绍 目前市面上主流的&#xff0c;成本最低的部署本地大模型的方法就是通过 Ollama 了&#xff1a; Ollama 是一个开源的本地大语言模型运行框架&#xff0c;专为在本地机器上便捷部署和运行大型语言模型&#xff08;LLM&#xff09;而设计。 核心…

idea自动编译,idea不重启项目,加载修改的内容

idea自动编译&#xff0c;idea不重启项目&#xff0c;加载修改的内容

幸运盒项目—测试报告

幸运盒测试报告 目录幸运盒测试报告一. 概要二. 测试环境三. 测试用例脑图四. 测试用例1. 功能测试1. 注册功能2. 密码登录功能3. 验证码登录功能4. 注册用户功能5. 创建奖品功能6. 新建抽奖活动功能8. 奖品列表9. 活动列表2. 界面测试1. 注册界面2. 密码登录界面3. 验证码登录…

Estimator and Confidence interval

Coefficient of determination and sample correlation coefficient R2SSRSSTR^2 \frac{SSR}{SST}R2SSTSSR​ SSR∑i1n((yi^−yˉ)2)SSR\sum_{i1}^n((\hat{y_{i}}-\bar{y})^2)SSR∑i1n​((yi​^​−yˉ​)2) SST∑i1n((yi−yˉ)2)SST\sum_{i1}^n((y_{i}-\bar{y})^2)SST∑i1n​…

【网络编程】TCP 服务器并发编程:多进程、线程池与守护进程实践

半桔&#xff1a;个人主页&#x1f525; 个人专栏: 《Linux手册》《手撕面试算法》《网络编程》&#x1f516;很多人在喧嚣声中登场&#xff0c;也有少数人在静默中退出。 -张方宇- 文章目录前言套接字接口TCP服务器TCP 多进程TCP 线程池重写Task任务放函数对象客户端重连进程…

还停留在批处理时代吗?增量计算架构详解

在当今的数字化环境中&#xff0c;企业不再只是一味地囤积数据——他们痴迷于尽快把数据转化为可付诸行动的洞察。真正的优势来自于实时发现变化并立即做出反应&#xff0c;无论是调整推荐策略还是规避危机。 十年前&#xff0c;硬件与平台技术的进步让我们能够从容应对海量数…