Go语言并发编程:sync、channel、goroutine的使用

238 阅读4分钟

Go语言并发编程:sync、channel、goroutine详解

Go语言以其高效的并发编程能力著称,而sync、channel和goroutine则是其并发编程的三大核心机制。理解和掌握这三种机制对于编写高性能、可扩展的Go语言程序至关重要。

1. goroutine:协程

goroutine是Go语言实现的轻量级线程,是一种比传统线程更轻便、更易管理的并发执行单元。每个goroutine都有自己的调用栈,可以独立运行。Go运行时会自动管理和调度goroutine,使得开发者无需过多关注底层的线程管理细节。

创建goroutine可以使用go关键字。例如:

Go

go func() {
  fmt.Println("Hello from goroutine!")
}()

这段代码将创建一个新的goroutine,并执行fmt.Println("Hello from goroutine!")语句。

goroutine的优势在于其轻便性和易用性。与传统线程相比,goroutine的创建和销毁代价更低,并且不需要额外的线程同步机制。这使得Go语言程序可以轻松地创建大量的goroutine,从而充分利用多核CPU的性能。

2. channel:通信与同步

channel是Go语言用于goroutine之间通信的同步机制。channel可以传输任意类型的数据,并且支持阻塞式和非阻塞式的通信方式。

创建channel可以使用make函数。例如:

Go

ch := make(chan int)

这段代码创建了一个名为ch的channel,该channel可以传输整数类型的数据。

向channel发送数据可以使用<-操作符。例如:

Go

ch <- 100

这段代码将数字100发送到channel ch中。

从channel接收数据可以使用->操作符。例如:

Go

v := <-ch
fmt.Println(v)

这段代码从channel ch中接收数据并将其赋值给变量v

channel还支持非阻塞式的通信方式。例如:

Go

select {
case v := <-ch:
  fmt.Println(v)
case <-time.After(time.Second):
  fmt.Println("Timeout")
}

这段代码使用select语句来实现非阻塞式的通信。如果channel ch中存在数据,则将其接收并输出;否则,等待1秒后超时并输出"Timeout"。

3. sync:同步与控制

sync包提供了各种同步机制,用于控制goroutine之间的协作。常用的sync机制包括:

  • sync.Mutex:用于互斥访问共享资源。
  • sync.WaitGroup:用于等待一组goroutine完成工作。
  • sync.Once:用于确保某项操作只执行一次。

例如,使用sync.Mutex来保护共享变量:

Go

var counter int
var mutex sync.Mutex

func increment() {
  mutex.Lock()
  defer mutex.Unlock()

  counter++
}

这段代码使用sync.Mutex来保护共享变量counter。在increment函数中,首先使用mutex.Lock()获取互斥锁,然后递增变量counter,最后使用mutex.Unlock()释放互斥锁。

4. 综合运用求素数

1. 未使用并发编程
package main

import (
	"fmt"
	"time"
)

// 判断是否是素数
func isPrime(num int) bool {
	if num <= 1 {
		return false
	}
	for i := 2; i*i <= num; i++ {
		if num%i == 0 {
			return false
		}
	}
	return true
}

func main() {
	start := time.Now().Unix()
	
	for i := 0; i < 5000000; i++ {
		if isPrime(i) {
			fmt.Println(i)
		}
	}

	end := time.Now().Unix()

	fmt.Printf("用时: %d 秒\n", end - start) // 用时: 0 秒

}

2. 使用并发编程
package main

import (
	"fmt"
	"runtime"
	"sync"
	"time"
)

// 判断是否是素数
func isPrime(num int) bool {
	if num <= 1 {
		return false
	}
	for i := 2; i*i <= num; i++ {
		if num%i == 0 {
			return false
		}
	}
	return true
}

// 设置数据
func setNmber(number int, ch chan int) {
	for i := 2; i < number; i++ {
		ch <- i
	}
	close(ch)
	mySync.Done()
}

// 将素数添加到单独的chan
func filterPrimes(ch chan int, primeCh chan int, filter chan bool) {
	for num := range ch {
		if isPrime(num) {
			primeCh <- num
		}
	}
	filter <- true
	if len(filter) == 10 {
		close(primeCh)
	}
	mySync.Done()
}

// 打印 耗时比较多
func printPrimes(primeCh chan int) {
	for prime := range primeCh {
		fmt.Println(prime)
	}
	mySync.Done()
}

// 确保协程都完成再结束main
var mySync sync.WaitGroup

func main() {
	numCPUs := runtime.NumCPU()
	fmt.Printf("cpu核数 of CPUs: %d\n", numCPUs)
	numChan := make(chan int, 1000)
	primeChan := make(chan int, 1000)
	filterChan := make(chan bool, 10) // 用来计算什么时候关闭primeChan chan,使用rang遍历chan时必须要关闭chan
  
	start := time.Now().Unix()
	mySync.Add(1)
	go setNmber(5000000, numChan)
	
	for i := 0; i < 10; i++ {
		mySync.Add(1)
		go filterPrimes(numChan, primeChan, filterChan) 
	}

	mySync.Add(1)
	go printPrimes(primeChan)

	mySync.Wait()
	end := time.Now().Unix()
	fmt.Printf("用时: %d 秒\n", end - start) // 用时: 0 秒
}

3. 执行速度对比
数据范围无并发用时(秒)并发用时(秒)
50000000
500000011
500000002113
500000000521182

总结

sync、channel和goroutine是Go语言并发编程的三大核心机制。理解和掌握这三种机制对于编写高性能、可扩展的Go语言程序至关重要。