Go 语言并发编程初体验:简洁高效

2024-05-07 18:07:59 浏览数 (2)

前言

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的数字中,哪些是素数。

传统方式实现

代码语言:go复制
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 机制,实现并发操作,相比其他编程语言简化了很多。

代码语言:go复制
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 的基本语法,可以对应上面的代码。

代码语言:go复制
//使用 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语言确实足够简洁高效,也证实了:可以在不损失应用程序性能的情况下降低代码的复杂性。

0 人点赞