Golang 并发编程详解

Golang 并发编程详解

介绍

并发是现代软件开发中的一个重要概念,它允许程序同时执行多个任务,提高系统的性能和响应能力。Golang 是一门天生支持并发的语言,它通过 goroutine 和 channel 提供了强大的并发编程支持。

在本文中,我们将深入探讨 Golang 中的并发编程,了解 goroutine、channel 以及一些常见的并发模式。

Goroutine

Goroutine 是 Golang 中的轻量级线程,由 Go 运行时系统调度。它们比传统的线程更轻便,创建和销毁的代价更小,使得在程序中创建成千上万个 goroutine 变得实际可行。

创建 Goroutine

在 Golang 中创建 goroutine 非常简单。只需在函数调用前使用关键字 go 就可以将其包装成 goroutine。

package main

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

func printNumbers() {
	for i := 1; i <= 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Printf("%d ", i)
	}
}

func main() {
	go printNumbers()

	// 主 goroutine 不等待 printNumbers 完成
	time.Sleep(1 * time.Second)

	fmt.Println("Main goroutine")
}

上述代码中,printNumbers 函数被包装成一个 goroutine,并在 main 函数中异步执行。time.Sleep(1 * time.Second) 用于等待足够的时间,确保 printNumbers 有足够的时间打印数字。注意,主 goroutine 并不会等待 printNumbers 执行完毕。

Channel

Channel 是 Golang 中用于在 goroutine 之间传递数据的管道。它提供了一种同步的方式,确保数据安全地在 goroutine 之间传递。

创建 Channel

在 Golang 中,使用 make 函数创建 channel。

ch := make(chan int)

上述代码创建了一个整数类型的无缓冲 channel。无缓冲 channel 在发送数据和接收数据时都会阻塞,直到另一方准备好。

使用 Channel

package main

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

func printNumbers(ch chan int, wg *sync.WaitGroup) {
	defer wg.Done()

	for i := 1; i <= 5; i++ {
		time.Sleep(100 * time.Millisecond)
		ch <- i
	}
	close(ch)
}

func main() {
	ch := make(chan int)
	var wg sync.WaitGroup

	wg.Add(1)
	go printNumbers(ch, &wg)

	for num := range ch {
		fmt.Printf("%d ", num)
	}

	fmt.Println("Main goroutine")
	wg.Wait()
}

上述代码中,printNumbers 将数字通过 channel 发送给主 goroutine,并在发送完毕后关闭 channel。主 goroutine 使用 for num := range ch 循环接收 channel 中的数据,直到 channel 被关闭。同时,使用等待组(sync.WaitGroup)确保所有 goroutine 执行完毕后主 goroutine 才结束。

并发模式

除了简单的 goroutine 和 channel 的使用外,Golang 还提供了一些常见的并发模式,如等待组、互斥锁等。这些模式帮助我们更好地组织和管理并发代码。

等待组

等待组(WaitGroup)用于等待一组 goroutine 执行完毕。它通过计数器实现,每个 goroutine 执行前递增计数器,执行完毕后递减计数器。主 goroutine 调用 Wait 方法等待计数器归零。

package main

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

func printNumbers(wg *sync.WaitGroup) {
	defer wg.Done()

	for i := 1; i <= 5; i++ {
		time.Sleep(100 * time.Millisecond)
		fmt.Printf("%d ", i)
	}
}

func main() {
	var wg sync.WaitGroup

	wg.Add(1)
	go printNumbers(&wg)

	wg.Wait()
	fmt.Println("Main goroutine")
}

互斥锁

互斥锁(Mutex)用于保护共享资源,防止多个 goroutine 同时访问导致的竞态条件。

package main

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

var counter = 0
var mutex sync.Mutex

func increment(wg *sync.WaitGroup) {
	defer wg.Done()

	for i := 0; i < 1000; i++ {
		mutex.Lock()
		counter++
		mutex.Unlock()
	}
}

func main() {
	var wg sync.WaitGroup

	wg.Add(2)
	go increment(&wg)
	go increment(&wg)

	wg.Wait()
	fmt.Printf("Final Counter: %d\n", counter)
}

上述代码中,两个 goroutine 并发地增加 counter 变量的值,使用互斥锁 sync.Mutex 来保护临界区,确保同一时刻只有一个 goroutine 能够访问。

使用 Select 处理多 Channel

在一些场景中,我们可能需要同时处理多个 channel,Golang 中提供了 select 语句来实现。

package main

import (
	"fmt"
	"time"
)

func producer(ch chan int, wg *sync.WaitGroup) {
	defer wg.Done()

	for i := 1; i <= 5; i++ {
		time.Sleep(100 * time.Millisecond)
		ch <- i
	}
	close(ch)
}

func consumer(ch1, ch2 chan int, wg *sync.WaitGroup) {
	defer wg.Done()

	for {
		select {
		case num, ok := <-ch1:
			if ok {
				fmt.Printf("From ch1: %d\n", num)
			}
		case num, ok := <-ch2:
			if ok {
				fmt.Printf("From ch2: %d\n", num)
			}
		}
	}
}

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	var wg sync.WaitGroup

	wg.Add(2)
	go producer(ch1, &wg)
	go producer(ch2, &wg)



	go consumer(ch1, ch2, &wg)

	wg.Wait()
	fmt.Println("Main goroutine")
}

上述代码中,consumer 函数通过 select 同时监听两个 channel(ch1ch2),一旦其中一个 channel 有数据,就进行处理。这种方式非常适用于处理多个 channel 的情况。

结语

通过本文,我们深入了解了 Golang 中的并发编程,包括 goroutine、channel 以及一些常见的并发模式。并发编程使得 Golang 在处理高并发任务时表现出色,开发者可以通过合理使用并发特性来提高程序性能。

希望读者通过本文的学习,能更好地应用 Golang 中的并发编程,构建高效、稳定的软件系统。

你可能感兴趣的:(golang,爬虫,开发语言)