网站首页 > 技术文章 正文
并发意味着程序在运行时有多个执行上下文,对应着多个调用栈。
每一个进程在运行时,都有自己的调用栈和堆,有一个完整的上下文。
从系统的角度讲,多个进程时可以并发的。
并发适用的场景有 4 种:
- 同时响应图形用户界面和 IO 密集操作
- 服务器面对大量用户请求
- 发挥计算机硬件的能力
- IO 操作阻塞程序
并发的优势有 3 条:
- 表现问题模型
- 提高程序的执行效率
- 充分利用固件的异步性
实现并发的主流模型包括 4 种:
- 多进程,传统并发模型
- 多线程,传统并发模型
- 基于回调的非阻塞/异步 IO,传统并发模型
- 协程
传统并发模型的缺陷包括 2 条:
- 共享内存的并发,容易在工程中遇到各种奇怪的故障和问题
- 消息传递系统,复制操作在性能上不优越。
新的并发方式:协程
Python 中的并发方式叫 协程,用 yield 实现,Go 中把它叫做 goroutine , 也称作协程。Go 中从语言的角度就支持协程。
goroutine 由 Go 的运行时 runtime管理。
使用并发执行一个 Add 函数
func Add(x, y int) {
z := x + y
fmt.Println(z)
}
go Add(1, 1)
在函数调用前加上 go 关键字,这次调用就会在一个新的 goroutine 中并发执行。
当被调用的函数返回时,这个 goroutine 也自动结束了。
当这个函数有返回值,在并发执行时,这个返回值会被丢弃。
package main
import "fmt"
func Add(x, y int) {
z := x + y
fmt.Println(z)
}
func main() {
for i := 0; i<10; i++ {
go Add(i,i)
}
}
想要并行执行 10 次 Add() 函数,可以通过在一个 for 循环中调用 10 次 Add()。但这样会出现协程还未开始执行,主程序就已经退出的情况。
Go 语言的程序机制是,程序从初始化 main package 并执行 main() 函数开始,当 main() 函数返回时,程序退出,且程序不等待其他 goroutine 结束。
解决的办法是使用 Go 的 channel:
package main
import "fmt"
import "sync"
import "runtime"
var counter int = 0
func Count(lock *sync.Mutex) {
lock.Lock()
counter++
fmt.Println(counter)
lock.Unlock()
}
func main() {
lock := &sync.Mutex{}
for i:=0; i<10; i++ {
go Count(lock)
}
for {
lock.Lock()
c := counter
lock.Unlock()
runtime.Gosched()
if c>=10 {
break
}
}
}
Go 中获知所有 goroutine 的状态,使用的是并发通信。
区别于共享数据的并发通信模型,Go 采用的是消息的模型。
package main
import "fmt"
func Count(ch chan int) {
fmt.Println("Counting") // 3.执行加减计算
ch <- 1 // 4.通过 ch <- 1 语句向对应的 channel 中写入数据 1,在这个 channel 被读取前,这个操作是阻塞的。
}
func main() {
chs := make([]chan int, 10) // 1.定义一个包含 10 个 channel 的数组
for i := 0; i<10; i++ { // 2.把数组中的每个 channel 分配给 10 个不同的 goroutine
chs[i] = make(chan int)
go Count(chs[i])
} // 5.在所有的 goroutine 启动完成后
for _, ch := range(chs) {
<-ch // 6.从 10 个 channel 中依次读取数据。在对应的 channel 写入数据前,这个操作也是阻塞的。
}
}
这样,用 channel 实现了类似锁的功能,进而保证了所有 goroutine 完成后,主函数才返回。
channel 的声明形式:
var chanName chan ElementType
与一般的变量声明不同的地方仅仅是在类型之前加了 chan 关键字。ElementType 指定这个 channel 所能传递的元素类型。
var ch chan int // 声明一个传递类型为 int 的 channel
var m map[string] chan bool // 声明一个 map,元素是 bool 型的 channel
定义一个 channel,直接使用内置的函数 make():
ch := make(chan int) // 声明并初始化一个 int 型的 channel,名为 ch
channel 用法中,最常见的包括写入和读出。
ch <- value // 数据写入至 channel
向 channel 写入数据会导致程序阻塞,直到有其他 goroutine 从这个 channel 中读取数据。
value := <- ch
如果 channel 之前没有写入数据,那么从 channel 中读取数据也会导致程序阻塞,直到 channel 中被写入数据为止。
- 上一篇: Go 语言结构 – 高级篇(递归、类型转换、接口、错误处理、并发)
- 下一篇: Go并发之原子操作
猜你喜欢
- 2024-12-01 Go 并发可视化解释 — 通道
- 2024-12-01 Go并发编程面试15题
- 2024-12-01 Go 语言并发编程实践:从入门到进阶
- 2024-12-01 Go语言编写的简单并发编程示例
- 2024-12-01 并发编程的奇技淫巧:Go语言调度器的内在工作机制
- 2024-12-01 Goroutine 并发调度模型深度解析之手撸一个高性能 goroutine 池
- 2024-12-01 每天2分钟学习GO语言编程(十九)并发简明教程
- 2024-12-01 并发编程,程序员必修课,来看go语言的巧妙实现,极为干练
- 2024-12-01 Go项目中如何限制并发数?Atomic必须掌握
- 2024-12-01 Go语言并发入门
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)