Go并发编程原理

我们先来搞懂并发与并行的概念

并发:同一时间做多件事情
并行:同一时间处理多件事情

“做”和“处理”的区别在于:做只是代表即将处理,但是只能选择一个。
比如有a和b两件事情需要做,并发是一个进程在0.02s内,前0.01s做a、后0.01s做b,同一时间只能处理一个事情。并行是两个进程,在前0.01s分别同时做了a和b。

所以单核状况下进程处理只能是并发,只不过cpu切换轮流处理速度太快了,导致你感觉像是并行处理的,其实非也。
在这里插入图片描述

在这里插入图片描述

Go 语言的协程实现被称之为 goroutine,由 Go 运行时管理,在 Go 语言中通过协程实现并发编程非常简单:我们可以在一个处理进程中通过关键字 go 启用多个协程,然后在不同的协程中完成不同的子任务,这些用户在代码中创建和维护的协程本质上是用户级线程,Go 语言运行时会在底层通过调度器将用户级线程交给操作系统的系统级线程去处理,如果在运行过程中遇到某个 IO 操作而暂停运行,调度器会将用户级线程和系统级线程分离,以便让系统级线程去处理其他用户级线程,而当 IO 操作完成,需要恢复运行,调度器又会调度空闲的系统级线程来处理这个用户级线程,从而达到并发处理多个协程的目的。此外,调度器还会在系统级线程不够用时向操作系统申请创建新的系统级线程,而在系统级线程过多的情况下销毁一些空闲的线程,这个过程和 PHP-FPM 的工作机制有点类似,实际上这也是很多进程/线程池管理器的工作机制,这样一来,可以保证对系统资源的高效利用,避免系统资源的浪费。

协程简单示例
下面通过一个简单的示例来演示如何在 Go 语言中通过协程进行并发编程,我们在 add.go 中编写一个加法函数 add 并通过协程的方式来调用它:

package main

import "fmt"

func add(a, b int) {
    var c = a + b
    fmt.Printf("%d + %d = %d", a, b, c)
}

func main() {
    go add(1, 2)
}

嗯,就是这么简单,在这段代码中包含了两个协程,一个是显式的,通过 go 关键字声明的这条语句,表示启用一个新的协程来处理加法运算,另一个是隐式的,即 main 函数本身也是运行在一个主协程中,该协程和调用 add 函数的子协程是并发运行的两个协程,就好比从 go 关键字开始,从主协程中叉出一条新路。和之前不使用协程的方式相比,由此也引入了不确定性:我们不知道子协程什么时候执行完毕,运行到了什么状态。在主协程中启动子协程后,程序就退出运行了,这就意味着包含这两个协程的处理进程退出了,所以,我们运行这段代码,不会看到子协程里运行的打印结果,因为还没来得及执行它们,进程就已经退出了。另外,我们也不要试图从 add 函数返回处理结果,因为在主协程中,根本获取不到子协程的返回值,从子协程开始执行起就已经和主协程没有任何关系了,返回值会被丢弃。

如果要显示出子协程的打印结果,一种方式是在主协程中等待足够长的时间再退出,以便保证子协程中的所有代码执行完毕:

package main

import (
    "fmt"
    "time"
)

func add(a, b int) {
    var c = a + b
    fmt.Printf("%d + %d = %d\n", a, b, c)
}

func main() {
    go add(1, 2)
    time.Sleep(1e9)
}

这里,我们通过 time.Sleep(1e9) 让主协程等待 1s 后退出,这样,运行 go run add.go 就可以看到打印结果了:
在这里插入图片描述
不过,这种方式过于简单粗暴,对于加法运算,1s 肯定够了(而且根本不需要这么长时间),但是如果是数据库连接、发送邮件之类的难以预估时间的操作呢?这种方式就不合适了,我们需要一种更精准的方式在子协程执行完毕后,立即退出主协程,这就涉及到协程间的通信,我们将在下一篇教程中重点讨论这一块,并且通过协程间通信来重写这段代码。

并发执行示例

目前为止,我们仅仅演示了 Go 语言协程的启动和简单使用,但是通过上述代码还不足以验证协程是并发执行的,接下来,我们通过下面这段代码来验证协程的并发执行:

package main

import (
    "fmt"
    "time"
)

func add(a, b int) {
    var c = a + b
    fmt.Printf("%d + %d = %d\n", a, b, c)
}

func main() {
    for i := 0; i < 10; i++ {
        go add(1, i)
    }
    time.Sleep(1e9)
}

很简单,我们给协程代码外层套上一个循环就可以了,这样一来,就同时启动了 10 个子协程,由于它们是并发执行的,执行的先后顺序无法保证,所以我们就看到了这样的打印结果:
在这里插入图片描述

<think>嗯,用户让我介绍一下并发编程原理。首先,我需要明确用户可能的需求。他们可能正在学习编程,或者在实际开发中遇到了并发相关的问题,想要了解基础概念。用户可能是学生、刚入行的开发者,或者有一定经验但对并发不太熟悉的人。 接下来,我得考虑从哪里开始介绍。并发编程涉及很多概念,比如进程、线程、协程,还有同步、锁、死锁等等。得先理清这些基本术语,不然用户可能会混淆。要确保解释清晰,用简单易懂的中文,避免太专业的术语,或者在必要时加以说明。 然后,用户可能需要了解为什么需要并发编程。这时候应该说明并发的优势,比如提高资源利用率、提升响应速度等。同时也要提到并发带来的挑战,比如竞态条件、死锁、调试困难,这样用户能全面理解利弊。 同步机制是关键部分,需要详细解释。比如互斥锁、信号量、条件变量,这些是处理并发问题的基本工具。要举例说明它们的使用场景,比如互斥锁保护共享资源,信号量控制资源数量,条件变量用于线程间通信。 另外,现代编程语言对并发的支持也很重要。比如Java的线程和锁,Gogoroutine和channel,Python的asyncio。用户可能想知道不同语言的处理方式,所以需要简要比较,帮助用户根据实际需要选择工具。 还有,异步编程模型和并发设计模式,如生产者-消费者、线程池,这些是实际开发中常用的模式,需要提到并解释其作用。比如线程池如何管理资源,避免频繁创建销毁线程的开销。 最后,应该给出学习建议,推荐经典书籍和在线资源,帮助用户进一步学习。这部分要权威且实用,比如《Java并发编程实战》或者Go的官方文档。 需要注意的是,用户可能对某些概念有初步了解,但需要系统化的整理。因此,结构要清晰,分点讲解,逐步深入。同时,避免过于深入细节,保持概述的性质,让用户有一个全面的认识,再自行深入感兴趣的部分。 可能还需要检查是否有遗漏的重要概念,比如原子操作、内存可见性、线程安全的数据结构等。这些也是并发编程中不可忽视的部分,应该在适当的位置提到,但不要过于复杂。 总结下来,回答的结构应该是:定义并发编程,为什么需要它,核心概念,同步机制,常见问题,现代语言的支持,设计模式,以及学习资源。这样用户能循序渐进地理解并发编程原理和应用。</think>以下是关于并发编程原理的逐步解释: 1. 基本定义 并发编程是指在同一时间段内处理多个计算任务的编程方式。与串行执行不同,并发允许任务交替执行(单核)或并行执行(多核),典型场景包括: - Web服务器处理多个客户端请求 - 游戏引擎同时处理物理计算和渲染 - 数据处理程序的MapReduce任务 2. 核心概念 ├─ 进程(Process) │ 独立内存空间的执行单位(Chrome每个标签页是独立进程) ├─ 线程(Thread) │ 共享进程资源的轻量级执行流(Java中的Thread类) ├─ 协程(Coroutine) │ 用户态轻量级线程(Go语言的goroutine) └─ 并发 vs 并行 并发:逻辑上的同时处理(单核交替执行) 并行:物理上的同时执行(多核真正并行) 3. 同步机制 √ 互斥锁(Mutex) 示例:Java的synchronized代码块 ```java public class Counter { private int value; private final Object lock = new Object(); public void increment() { synchronized(lock) { value++; } } } ``` √ 信号量(Semaphore) 控制同时访问资源的线程数量(如数据库连接池) √ 条件变量(Condition) 实现线程间状态通知(生产者-消费者模式) 4. 常见挑战 ⚠️ 竞态条件(Race Condition) 当两个线程同时修改共享数据时: ```python # 错误示例 counter = 0 def increment(): global counter temp = counter temp += 1 counter = temp ``` ⚠️ 死锁(Deadlock) 四个必要条件: 1. 互斥访问 2. 持有并等待 3. 不可抢占 4. 循环等待 5. 现代解决方案 │ 语言/框架 │ 特性 │ 示例场景 │ ├──────────────┼───────────────────────┼─────────────────────┤ │ Java │ CompletableFuture │ 异步HTTP请求聚合 │ │ Gogoroutine + channel │ 高并发网络服务 │ │ Python │ asyncio事件循环 │ Web爬虫并发下载 │ 6. 学习建议 - 实践项目:实现线程安全的队列、模拟票务系统 - 调试工具:Java VisualVM、Go的pprof - 经典书籍:《Java并发编程实战》《Go并发编程实战》 理解并发编程需要结合操作系统原理与具体语言特性,建议从线程安全的基本概念入手,逐步深入探索锁优化、无锁编程等高级主题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值