Golang并发编程基础教程:从Goroutines入门到实战应用

1. Goroutines介绍

在Golang中,Goroutines是在多线程并发编程的基础上设计的一种轻量级的线程。它可以在非常小的栈内存中运行,而且创建速度极快,也不需要开发者手动管理线程

在Golang中启动一个Goroutine只需要在函数调用前添加go关键字即可,例如:

func printNums() {

for i := 0; i < 10; i++ {

fmt.Println(i)

}

}

func main() {

go printNums()

}

上述代码中,我们通过在printNums()函数前加go关键字来启动一个Goroutine,在主函数中调用它。这样,printNums()函数会在独立的线程中运行,而主线程也会继续执行。这种并行处理可以提高程序的效率。

2. Channel通信机制

Goroutines通过Channel来进行通信。Channel是一种类型,它可以用于在Goroutines之间传递数据,相当于Go语言中的管道。Channel有两种类型:

2.1 单向Channel

单向Channel只能发送或只能接收数据,用于控制数据的流向。定义单向Channel有如下方式:

// 只能接收int类型的消息的channel

var ch1 <-chan int

// 只能发送int类型的消息的channel

var ch2 chan<- int

2.2 双向Channel

双向Channel既能发送也能接收数据,定义方式如下:

// 可以双向收发int类型的消息的channel

var ch3 chan int

通过使用Channel,我们可以在不同的Goroutines中交换数据,加快程序的运行速度,提高程序的性能。以下是一个使用Channel的简单示例:

package main

import (

"fmt"

)

func sendMessage(ch chan string) {

ch <- "Hello World!"

}

func main() {

ch := make(chan string)

go sendMessage(ch)

msg := <-ch

fmt.Println(msg)

}

在上述例子中,我们通过ch <- "Hello World!"向Channel发送了一条消息,在主函数中通过msg := <-ch接收了这条消息,并通过fmt.Println()函数输出了它。

3. Mutex互斥锁

Golang中的Mutex(互斥锁)用于保护共享资源,确保在同一时间内只有一个Goroutine可以访问它。在使用Mutex时,需要通过Lock()方法锁定资源,通过Unlock()方法解锁资源,例如:

var mutex sync.Mutex

func add() {

mutex.Lock()

defer mutex.Unlock()

// 对共享资源进行操作

}

在上述代码中,我们定义了一个名为mutex的互斥锁,使用Lock()方法锁定共享资源,在函数执行完毕后通过defer关键字调用Unlock()方法释放锁。

4. 实战应用

利用Goroutines和Channel实现一个并发计算素数的程序。

4.1 程序简介

该程序会先生成一段"素数筛"数组,然后通过Goroutines进行并发计算。每个Goroutine会从Channel中取出当前的素数并将它的倍数从筛子中去除,直到没有剩余的素数。最终筛子中剩下的数字即为所有素数。

4.2 具体实现

以下是具体的程序实现:

package main

import (

"fmt"

"math"

)

func makePrimes(max int) ([]int, chan int) {

primes := []int{}

ch := make(chan int)

go func() {

for i := 2; i <= max; i++ {

primes = append(primes, i)

ch <- i

}

close(ch)

}()

return primes, ch

}

func filterPrimes(primes []int, ch chan int) []int {

limit := int(math.Sqrt(float64(len(primes))))

for i := 0; i <= limit; i++ {

prime := primes[i]

filtered := []int{}

for j := i + 1; j < len(primes); j++ {

if primes[j]%prime != 0 {

filtered = append(filtered, primes[j])

}

}

primes = filtered

ch <- prime

}

close(ch)

return primes

}

func main() {

max := 30

allPrimes, primeCh := makePrimes(max)

filteredPrimes := filterPrimes(allPrimes, primeCh)

fmt.Println("Primes less than or equal to", max, ":")

for prime := range filteredPrimes {

fmt.Println(prime)

}

}

在makePrimes()函数中,我们首先生成了一个包含2~max的整数数组,并通过循环迭代将它们依次添加到primes数组中。同时,我们也创建了一个名为primeCh的Channel,用于向其他Goroutines发送当前正在进行计算的素数。

在filterPrimes()函数中,我们使用了一种叫做埃拉托斯特尼筛法的算法,通过将当前素数的倍数从数组中剔除,来实现并发计算素数。在计算完成后,我们通过close()函数关闭Channel通道,告知其它Goroutines不再向Channel发送数据。

在main()函数中,我们调用makePrimes()函数生成初始整数数组和Channel,然后使用filterPrimes()函数进行计算,最终输出剩余的素数。

5. 总结

Golang中的Goroutines和Channel提供了非常方便的并发编程方式,并在一定程度上简化了多线程编程的难度。同时,互斥锁也可以保护共享资源,避免多个Goroutines同时访问造成的数据竞争问题。通过熟练掌握并发编程技术,我们可以运用它们来提高程序的效率和性能,快速解决并发编程的难题。

后端开发标签