编程 Go语言的并发编程,包括Mutex、RWMutex、WaitGroup和Channel等机制

2024-11-19 08:09:19 +0800 CST views 660

Go语言的并发编程,包括Mutex、RWMutex、WaitGroup和Channel等机制

在传统的编程语言中,如C++、Java、Python等,并发逻辑多建立在操作系统线程之上。线程间的通信通常依赖于操作系统提供的基础原语,包括共享内存、信号、管道、消息队列及套接字等,其中共享内存是最为普遍的通信方式。但这种基于共享内存的并发模型在复杂或大规模业务场景下往往显得复杂且易于出错。

Go语言在设计时即以解决传统并发问题为目标,融入了CSP(Communicating Sequential Processes,通信顺序进程)模型的理念。CSP模型致力于简化并发编程,目标是让编写并发程序的难度与顺序程序相当。

在CSP模型中,通信和同步通过一种特定的流程实现:生产者产生数据,然后通过输出数据到输入/输出原语,最终到达消费者。Go语言为实现CSP模型,特别引入了Channel机制。Goroutine可以通过Channel进行数据的读写操作,Channel作为连接多个Goroutine的通信桥梁,简化了并发编程的复杂性。

虽然CSP模型在Go语言中占据主流地位,但Go同样支持基于共享内存的并发模型。Go的sync包中,提供了包括互斥锁、读写锁、条件变量和原子操作等多种同步机制,以满足不同并发场景下的需求。

互斥锁(Mutex)

基本概念

互斥锁(Mutex)用于在并发环境中安全访问共享资源。当一个协程获取到锁时,它将拥有临界区的访问权,其他请求该锁的协程将会阻塞,直到锁被释放。

应用场景

并发访问共享资源的情形非常普遍,例如:

  • 秒杀系统
  • 多个Goroutine并发修改某个变量
  • 同时更新用户信息

如果没有互斥锁的控制,将会导致商品超卖、变量数值不正确、用户信息更新错误等问题。

基本用法

Mutex实现了Locker接口,提供了两个方法:LockUnlockLock用于对临界区上锁,Unlock用于释放锁。

package main

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

func main() {
	var mu sync.Mutex
	var count int

	increment := func() {
		mu.Lock()
		defer mu.Unlock()
		count++
		fmt.Println("Count:", count)
	}

	for i := 0; i < 5; i++ {
		go increment()
	}

	time.Sleep(time.Second)
}

易错场景

  • 不可重入锁:Go的Mutex是不可重入锁,持有锁的协程不能再次获取锁,否则会发生死锁。
  • Lock和Unlock不配对:未正确配对的调用会导致死锁。
  • 复制已使用的锁:复制已使用的锁会导致意外行为。

Mutex的状态和模式

Mutex有四种状态:mutexLocked(锁定)、mutexWoken(唤醒)、mutexStarving(饥饿模式)、waiterCount(等待者数量)。在正常模式下,新请求的Goroutine与被唤醒的Goroutine竞争锁,而饥饿模式则优先唤醒等待队列中的Goroutine。

读写锁(RWMutex)

基本概念

RWMutex是一种读写锁,同一时间只能被一个写操作持有,或者被多个读操作持有。在读多写少的场景下,它比Mutex更高效。

基本用法

RWMutex提供了Lock(写锁)、Unlock(释放写锁)、RLock(读锁)和RUnlock(释放读锁)等方法。

package main

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

func main() {
	var rw sync.RWMutex
	var count int

	write := func() {
		rw.Lock()
		defer rw.Unlock()
		count++
		fmt.Println("Write:", count)
	}

	read := func() {
		rw.RLock()
		defer rw.RUnlock()
		fmt.Println("Read:", count)
	}

	for i := 0; i < 5; i++ {
		go read()
	}

	go write()

	time.Sleep(time.Second)
}

易错场景

Mutex类似,RWMutex也是不可重入锁,未配对的LockUnlock调用也会导致死锁。

WaitGroup

基本概念

WaitGroup用于等待一组Goroutine完成。通过Add方法增加计数,Done方法减少计数,Wait方法阻塞等待计数归零。

package main

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

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Printf("Worker %d starting\n", id)
	time.Sleep(time.Second)
	fmt.Printf("Worker %d done\n", id)
}

func main() {
	var wg sync.WaitGroup

	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go worker(i, &wg)
	}

	wg.Wait()
	fmt.Println("All workers done")
}

Channel

基本概念

Go通过Channel实现Goroutine之间的通信。Channel分为无缓冲和有缓冲两种。close关闭Channel,发送和接收都可以作为select语句的case

应用场景

Channel常用于数据传递、并发编排和实现信号通知等。

package main

import (
	"fmt"
	"time"
)

func producer(ch chan<- int, count int) {
	for i := 0; i < count; i++ {
		ch <- i
		fmt.Println("Produced:", i)
		time.Sleep(time.Millisecond * 500)
	}
	close(ch)
}

func consumer(ch <-chan int) {
	for data := range ch {
		fmt.Println("Consumed:", data)
		time.Sleep(time.Millisecond * 1000)
	}
}

func main() {
	ch := make(chan int, 5)
	go producer(ch, 10)
	consumer(ch)
}

小结

Go语言通过Mutex、RWMutex、WaitGroup和Channel等工具,为并发编程提供了强大的支持。每种机制都有其独特的应用场景和注意事项,合理使用这些工具可以帮助开发者编写更高效、安全的并发程序。

复制全文 生成海报 编程 Go语言 并发

推荐文章

批量导入scv数据库
2024-11-17 05:07:51 +0800 CST
Vue3中如何进行错误处理?
2024-11-18 05:17:47 +0800 CST
html5在客户端存储数据
2024-11-17 05:02:17 +0800 CST
在Vue3中实现代码分割和懒加载
2024-11-17 06:18:00 +0800 CST
js生成器函数
2024-11-18 15:21:08 +0800 CST
imap_open绕过exec禁用的脚本
2024-11-17 05:01:58 +0800 CST
js常用通用函数
2024-11-17 05:57:52 +0800 CST
php 统一接受回调的方案
2024-11-19 03:21:07 +0800 CST
Vue3中如何进行性能优化?
2024-11-17 22:52:59 +0800 CST
Vue3 结合 Driver.js 实现新手指引
2024-11-18 19:30:14 +0800 CST
Go 接口:从入门到精通
2024-11-18 07:10:00 +0800 CST
Vue3中的虚拟滚动有哪些改进?
2024-11-18 23:58:18 +0800 CST
Vue3中如何处理跨域请求?
2024-11-19 08:43:14 +0800 CST
JavaScript 上传文件的几种方式
2024-11-18 21:11:59 +0800 CST
2024年微信小程序开发价格概览
2024-11-19 06:40:52 +0800 CST
程序员茄子在线接单