文章目录
- 前言
- GoLang 并发编程基本概念
- 进程与线程
- 线程和协程
- 并行与并发
- GoLang的协程机制
- GoLang 并发实践
- 案例需求
- 传统方式实现
- 使用 goroutines 实现并发
- goroutine 如何通信
- channel 使用注意事项
- 总结
前言
Go语言是谷歌推出的一种的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。
谷歌软件工程师罗布派克(Rob Pike)说:
我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮丧。和今天的C++或C一样,Go是一种系统语言,使用它可以进行快速开发。
以下是为什么开发Go的原因:
- 计算机硬件技术更新频繁,性能提高很快。目前主流的编程语言发展明显落后于硬件,不能合理利用多核多CPU的优势提升软件系统性能。
- 软件系统复杂度越来越高,维护成本越来越高,目前缺乏一个足够简洁高效的编程语言。
- 企业运行维护很多c/c++的项目,c/c++程序运行速度虽然很快,但是编译速度确很慢,同时还存在内存泄漏的一系列的困扰需要解决。
GoLang 并发编程基本概念
进程与线程
进程定义:进程是并发执行的程序中分配和管理资源的基本单位。
线程定义:线程是进程的执行单元,是进行调度的实体,是比进程更小的独立运行单位。
线程和协程
协程是单线程下的并发,又称微线程、纤程。它是实现多任务的另一种方式,只不过是比线程更小的执行单元。
线程的切换是一个cpu在不同线程中来回切换,是从系统层面来,不止保存和恢复CPU上下文这么简单,会非常耗费性能。
而协程只是在同一个线程内来回切换不同的函数,只是简单的操作CPU的上下文,所以耗费的性能会大大减少。
并行与并发
并发定义:多线程交替操作同一资源类。
并行定义:多个线程同时操作多个资源类。
Erlang 之父 Joe Armstrong 用一张5岁小孩都能看懂的图解释了并发与并行的区别。
GoLang的协程机制
GoLang的协程机制(Goroutines),是一种非常强大的并发编程机制,它可以用来实现高效的并发操作,可轻松开启上万个协程。
占用内存远比 Java 、C 的线程少:
- goroutine: 2KB
- 线程:8MB
GoLang 并发实践
案例需求
统计1~2000000的数字中,哪些是素数。
传统方式实现
package main
import (
"fmt"
"math"
)
func isPrime(num int) bool {
if num <= 1 {
return false
}
for j := 2; j <= int(math.Floor(math.Sqrt(float64(num)))); j++ {
if num%j == 0 {
return false
}
}
return true
}
func main() {
num := 17 // 示例数字
if isPrime(num) {
fmt.Printf("数字 %d 是素数。\n", num)
} else {
fmt.Printf("数字 %d 不是素数。\n", num)
}
}
isPrime
函数接受一个整数num
并返回一个布尔值,表示该数字是否为素数。在main
函数中调用isPrime
并打印出相应的结果。
使用 goroutines 实现并发
通过 goroutines 机制实现并发执行,提高执行效率。
直接通过 go 关键字就可以引入 goroutines 机制,实现并发操作,相比其他编程语言简化了很多。
package main
import (
"fmt"
"math"
)
var intChan chan int = make(chan int, 20000)
func main() {
var primeChan chan int = make(chan int, 20000)
var exitChan chan bool = make(chan bool, 8)
go initChan(20000)
for i := 0; i <= 8; i++ {
go isPrimeA(intChan, primeChan, exitChan)
}
go func() { //0.839 seconds
for i := 0; i <= 8; i++ {
<-exitChan
}
close(primeChan)
}()
for {
res, ok := <-primeChan
if !ok {
break
}
fmt.Println("素数:", res)
}
}
func initChan(num int) {
for i := 1; i <= num; i++ {
intChan <- i
}
close(intChan)
}
func isPrimeA(intChan chan int, primeChan chan int) {
for num := range intChan {
if num <= 1 {
continue
}
sqrtNum := int(math.Floor(math.Sqrt(float64(num))))
isPrime := true
for j := 2; j <= sqrtNum; j++ {
if num%j == 0 {
isPrime = false
break
}
}
if isPrime {
primeChan <- num
}
}
}
func isPrime(num int) {
for i := 1; i < num; i++ {
var flag bool = true
for j := 2; j < i; j++ {
if i%j == 0 {
flag = false
continue
}
}
if flag {
fmt.Println("数字", i, "是素数。")
}
}
}
如果启动了多个 goroutine,它们之间如何通信?
goroutine 如何通信
在 Go 语言中,channel(通道) 用于在 goroutine 之间进行通信,类似于其他编程语言中的队列或管道。
goroutine 之间的通信不需要加锁,因为通道本身线程安全。
下面是 channel 的基本语法,可以对应上面的代码。
//使用 chan 关键字声明一个 channel。
var ch chan int
// 或者,在声明时直接初始化
ch := make(chan int)
//使用 <- 操作符将数据发送到 channel。
ch <- 42
//使用 <- 操作符从 channel 接收数据。
num := <-ch
// 关闭 channel:
close(ch)
channel 使用注意事项
1.声明之后需要make开辟内存才可以使用。
2.如果写满了,继续写会报错。
//fatal error: all goroutines are asleep - deadlock!
总结
通过GoLang的协程机制(Goroutines),可以看到Go语言确实足够简洁高效,也证实了:可以在不损失应用程序性能的情况下降低代码的复杂性。