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

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

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语言 并发

推荐文章

软件定制开发流程
2024-11-19 05:52:28 +0800 CST
php内置函数除法取整和取余数
2024-11-19 10:11:51 +0800 CST
使用xshell上传和下载文件
2024-11-18 12:55:11 +0800 CST
java MySQL如何获取唯一订单编号?
2024-11-18 18:51:44 +0800 CST
php客服服务管理系统
2024-11-19 06:48:35 +0800 CST
liunx服务器监控workerman进程守护
2024-11-18 13:28:44 +0800 CST
Vue3中的JSX有什么不同?
2024-11-18 16:18:49 +0800 CST
纯CSS实现3D云动画效果
2024-11-18 18:48:05 +0800 CST
js生成器函数
2024-11-18 15:21:08 +0800 CST
从Go开发者的视角看Rust
2024-11-18 11:49:49 +0800 CST
设置mysql支持emoji表情
2024-11-17 04:59:45 +0800 CST
html折叠登陆表单
2024-11-18 19:51:14 +0800 CST
【SQL注入】关于GORM的SQL注入问题
2024-11-19 06:54:57 +0800 CST
Vue3中的v-bind指令有什么新特性?
2024-11-18 14:58:47 +0800 CST
Vue3中如何处理组件间的动画?
2024-11-17 04:54:49 +0800 CST
微信内弹出提示外部浏览器打开
2024-11-18 19:26:44 +0800 CST
小技巧vscode去除空格方法
2024-11-17 05:00:30 +0800 CST
Dropzone.js实现文件拖放上传功能
2024-11-18 18:28:02 +0800 CST
pip安装到指定目录上
2024-11-17 16:17:25 +0800 CST
程序员茄子在线接单