当前位置: 首页 > news >正文

一篇文章搞懂GO并发编程!

概要

本文旨在快速了解golang的并发编程代码实际编写, 至于理论方面不会讲解太多
我将以题目的形式来进行讲解
带有goroutine waitGroup channel select mutex RWMutex
以及生产-消费者模型的练习

goroutine

关于Goroutine的基本使用

启动多个 Goroutine 打印数字

编写一个程序,启动 5 个 Goroutine,每个 Goroutine 分别打印一个从 1 到 5 的数字。要求每个 Goroutine 独立运行。

用法提示:

Goroutine 是 Go 并发的基本单元,使用 go 关键字启动。它们独立执行,不会阻塞主线程,但需要注意主 Goroutine 的生命周期。Goroutines 不会自动同步,需要配合其它同步手段来控制程序的退出或结果。

答案
package mainimport "time"func main() {go func() {println(1)}()go func() {println(2)}()go func() {println(3)}()go func() {println(4)}()go func() {println(5)}()time.Sleep(2000 * time.Millisecond)}

WaitGroup

使用 sync.WaitGroup 等待 Goroutines 完成
在这个题目中,你需要修改上一个程序,使用 sync.WaitGroup 来替代 time.Sleep(),确保主 Goroutine 等待所有 Goroutines 完成后才退出。

用法提示:

sync.WaitGroup 是 Go 中用于等待一组 Goroutines 完成的同步原语。可以通过 Add() 方法增加等待的 Goroutine 数量,通过 Done() 方法减少计数,最后通过 Wait() 方法阻塞主 Goroutine,直到所有 Goroutine 执行完毕。

var wg sync.WaitGroup
wg.Add(1)     // 增加 Goroutine 计数
go func() {defer wg.Done()  // Goroutine 完成时调用 Done 减少计数// 执行任务
}()
wg.Wait()    // 阻塞主 Goroutine,直到计数归零
答案
package mainimport "sync"func main() {var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()println(1)}()wg.Add(1)go func() {defer wg.Done()println(2)}()wg.Add(1)go func() {defer wg.Done()println(3)}()wg.Add(1)go func() {defer wg.Done()println(4)}()wg.Add(1)go func() {defer wg.Done()println(5)}()wg.Wait()}

channel

使用 Channel 实现 Goroutines 间的通信
编写一个程序,启动两个 Goroutines,一个负责生成 1 到 10 的数字,另一个 Goroutine 负责接收这些数字并将它们打印出来。要求这两个 Goroutines 通过 Channel 通信。

用法提示:

Channel 是 Go 并发编程中用于 Goroutines 之间通信的机制。通过 Channel,可以安全地在多个 Goroutine 之间传递数据。使用 make(chan T) 创建一个类型为 T 的 Channel。通过 <- 操作符发送和接收数据。

ch := make(chan int) // 创建一个整型 Channelgo func() {ch <- 10  // 发送数据到 Channel
}()value := <-ch  // 从 Channel 接收数据

注意这里需要close关闭channel告知接收方不会再有数据传入了, 不然会有死锁风险

答案
package mainimport "sync"func main() {ch := make(chan int)var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()for i := 1; i <= 10; i++ {ch <- i}close(ch) // 关闭channel, 通知接收方没有更多的数据了}()wg.Add(1)go func() {defer wg.Done()for i := 1; i <= 10; i++ {println(<-ch)}}()wg.Wait()}

bufferedChannel

使用 Buffered Channel 实现并发任务结果收集
编写一个程序,启动 3 个 Goroutines,每个 Goroutine 执行一个计算任务,将它们的计算结果发送到一个 缓冲区大小为 3 的 Channel 中。主 Goroutine 需要从 Channel 中读取所有结果并打印出来。每个 Goroutine 的任务如下:

第一个 Goroutine 计算从 1 到 5 的和。
第二个 Goroutine 计算从 6 到 10 的和。
第三个 Goroutine 计算从 11 到 15 的和。
主 Goroutine 读取 Channel 中的结果并打印每个任务的计算结果。

用法提示

使用 Buffered Channel 来确保发送数据不会阻塞 Goroutines。
make(chan int, 3):创建一个带有 3 个缓冲区的 Channel。
各个 Goroutine 完成计算后,将结果发送到 Channel 中。

ch := make(chan int, 3) // 创建一个缓冲区大小为 3 的 Channelch <- 1  // 向缓冲区发送数据,未被消费时不会阻塞,直到缓冲区满
ch <- 2
ch <- 3value := <-ch  // 消费数据,缓冲区释放
错误示范
package mainimport ("fmt""sync"
)// func main() {
func A() {ch := make(chan int, 3)var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()var sum intfor i := 1; i <= 5; i++ {sum += i}ch <- sum}()wg.Add(1)go func() {defer wg.Done()var sum intfor i := 6; i <= 10; i++ {sum += i}ch <- sum}()wg.Add(1)go func() {defer wg.Done()var sum intfor i := 11; i <= 15; i++ {sum += i}ch <- sum}()wg.Add(1)go func() {defer wg.Done()var sum intfor i := 1; i <= 3; i++ {sum += <-ch}fmt.Println(sum)}()wg.Wait()/*在最后一个 Goroutine 中,尝试计算所有 Goroutines 传回的和,但这个 Goroutine 和前面计算任务的 Goroutines 是同时执行的。如果主 Goroutine提前开始读取 Channel 数据,其他 Goroutines 还没有来得及发送数据,这就可能导致程序行为不符合预期,甚至可能出现死锁*/
}
答案
package mainimport ("fmt""sync"
)func main() {ch := make(chan int, 3)var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()var sum intfor i := 1; i <= 5; i++ {sum += i}ch <- sum}()wg.Add(1)go func() {defer wg.Done()var sum intfor i := 6; i <= 10; i++ {sum += i}ch <- sum}()wg.Add(1)go func() {defer wg.Done()var sum intfor i := 11; i <= 15; i++ {sum += i}ch <- sum}()go func() {wg.Wait()close(ch)}()var ans intfor sum := range ch {ans += sum}fmt.Println(ans)
}

当主程序执行 for sum := range ch { ans += sum } 时,ch 最终一定会被关闭。
具体来说,主 Goroutine 会在执行 wg.Wait() 之后通过 close(ch) 关闭 Channel。因为 close(ch) 是在一个 Goroutine 中执行的,而 wg.Wait() 会阻塞直到所有的计算 Goroutines 完成,因此只有当所有 Goroutines 都执行完毕后,才会执行 close(ch),然后主程序才开始遍历 Channel。

生产者-消费者模式

编写一个程序,实现生产者-消费者模式。创建一个缓冲区大小为 5 的 Channel,启动一个生产者 Goroutine 和一个消费者 Goroutine。

生产者:

生成 20 个整数(从 1 到 20),并将它们发送到 Channel。
每次发送后暂停一小段时间(可以使用 time.Sleep),模拟生产的延迟。
消费者:

从 Channel 中接收数据,并打印接收到的整数。
每次接收后同样暂停一小段时间,模拟消费的延迟。
在生产者完成发送后,关闭 Channel,通知消费者没有更多数据会发送。

用法提示

使用 Buffered Channel 来避免生产者和消费者之间的阻塞。
生产者可以在 Channel 未满时继续发送数据,消费者可以在 Channel 非空时继续接收数据。

答案
package mainimport ("fmt""sync""time"
)func main() {ch := make(chan int, 5)var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()for i := 1; i <= 20; i++ {ch <- itime.Sleep(100 * time.Millisecond)}close(ch)}()wg.Add(1)go func() {defer wg.Done()for {v, ok := <-chtime.Sleep(100 * time.Millisecond)if !ok {break}fmt.Println("正在消费信息", v)}}()// 还可以这样写消费者, 当ch还没有被close的时候, 消费者如果消费完了全部数据会阻塞等待ch被关闭// 这个for的退出取决于 数据是否被读取完毕&&管道是否被close//wg.Add(1)//go func() {//	defer wg.Done()//	for v := range ch { // 使用 range 读取 Channel 数据//		fmt.Println("正在消费信息", v)//		time.Sleep(100 * time.Millisecond) // 模拟消费延迟//	}//}()wg.Wait()
}

select

编写一个程序,其中包含两个生产者和两个消费者,使用 select 语句来处理多个 Channel 的读取和超时情况。
具体要求:
创建 Channel:
创建一个带缓冲的整型 Channel,容量为 5。
生产者:
启动两个生产者 Goroutines。
每个生产者每隔 1 秒向 Channel 中发送一个从 1 到 20 的整数(生产者可以选择不同的数字)。
当每个生产者发送完各自的 10 个数字后,关闭 Channel。
消费者:
启动两个消费者 Goroutines。
每个消费者从 Channel 中读取数据并打印出来。
使用 select 语句来处理 Channel 的接收操作,同时设置一个 5 秒的超时,如果在这段时间内没有接收到数据,消费者应该打印 “超时,未收到数据” 并结束。
结束条件:

每个消费者应在成功消费 10 个数字后停止运行。
确保在关闭 Channel 之前,消费者能够正常退出并不发生阻塞。

用法提示

select 语句用于等待多个 Channel 操作,可以用来处理发送和接收数据的操作。
使用 time.After 来设置超时,这样可以在等待 Channel 数据的同时处理超时事件。

答案
package mainimport ("fmt""math/rand""sync""time"
)func main() {ch := make(chan int, 5)var wg sync.WaitGroupwg.Add(1)go func() {defer wg.Done()for i := 1; i <= 10; i++ {ch <- rand.Intn(3) + 1}}()wg.Add(1)go func() {defer wg.Done()for i := 1; i <= 10; i++ {ch <- rand.Intn(3) + 1time.Sleep(10 * time.Second) // 模拟超时}}()go func() {wg.Wait()close(ch)}()for {select {case v, ok := <-ch:if !ok {return}fmt.Println("消费者处理数据", v)case <-time.After(5 * time.Second):fmt.Println("5秒内没有收到数据")return}}}

Mutex

编写一个程序,启动 500 个 Goroutines,每个 Goroutine 都要对同一个全局计数器 counter 进行 100 次递增操作。由于多个 Goroutine 会同时访问 counter,你需要使用 sync.Mutex 来确保并发安全。

用法提示

sync.Mutex 用于保护共享资源。
使用 mutex.Lock() 锁定共享资源,确保只有一个 Goroutine 能够访问该资源。
使用 mutex.Unlock() 解锁,允许其他 Goroutine 访问共享资源。

答案
package mainimport ("fmt""sync"
)func main() {// 如果不加锁 结果会是怎么样的var mutex sync.Mutexvar count intvar wg sync.WaitGroupfor i := 1; i <= 500; i++ {wg.Add(1)go func() {defer wg.Done()for j := 1; j <= 100; j++ {mutex.Lock()// 当此处不加锁, 多个goroutine并发执行count++// A协程读取count为15531   A协程计算+1结果   A程序把15532值赋给count// -----------------------------------------------------------------//                  B协程读取count为15531     B协程计算+1结果  B程序把15532值赋给count// B协程读取的时候A协程还未把修改结果赋值, 导致了B协程的修改无效count++mutex.Unlock()}}()}wg.Wait()fmt.Println(count)}

RWMytex

使用 sync.RWMutex 实现读写锁
编写一个程序,模拟多个 Goroutines 对共享变量的读写操作。要求:
启动 5 个 Goroutines,分别执行读操作,每次读取需要通过 sync.RWMutex 确保安全,且多个读操作可以并发执行。
启动 2 个 Goroutines,分别执行写操作,每次写操作需要通过 sync.RWMutex 进行独占锁控制,确保写操作期间其他 Goroutines 无法读或写。

用法提示

rwMutex.RLock() 用于加读锁,多个 Goroutine 可以同时获取读锁。
rwMutex.RUnlock() 用于释放读锁。
rwMutex.Lock() 用于加写锁,写操作期间其他 Goroutine 无法进行读或写。
rwMutex.Unlock() 用于释放写锁。

答案
package mainimport ("fmt""sync"
)type node struct {mutex sync.RWMutexcount int
}func main() {var c nodevar wg sync.WaitGroupfor i := 1; i <= 50; i++ {wg.Add(1)go func() {defer wg.Done()c.mutex.Lock()defer c.mutex.Unlock()c.count++}()}wg.Wait()for i := 1; i <= 200; i++ {wg.Add(1)go func() {defer wg.Done()c.mutex.RLock()defer c.mutex.RUnlock()fmt.Println(c.count)}()}wg.Wait()
}

http://www.mrgr.cn/news/58517.html

相关文章:

  • 经典功率谱估计的原理及MATLAB仿真(自相关函数BT法、周期图法、bartlett法、welch法)
  • 【python】OpenCV—Tracking(10.3)——GOTURN
  • 再战df内容回显
  • 深入浅出理解BLE AUDIO CSIS
  • 百钱买百鸡问题
  • 延迟队列实现及其原理详解
  • 15-01 mave高级-分模块设计与开发
  • Python基础14_Pandas(下)
  • 多态(作业篇)
  • python算法学习笔记之查找算法
  • 2:ARM 汇编语言2:二进制/十进制/十六进制
  • RBM HA联动VRRP三层主备案例
  • 从天边到身边,‘湘’遇北斗,‘株’多精彩
  • 状态栏黑底白字后如何实现圆角以及固定状态栏
  • golang的net包
  • vue2脚手架搭建项目流程
  • 3.1 机器学习--线性回归
  • JAVA基础-泛型
  • FineReport 多数据源报表
  • 搞fastjson总是惦记TemplatesImpl谁懂
  • SpingBoot原理
  • 线性表->链表(数据结构)
  • 在Android开发中WebView的详细使用方法
  • 【日常记录-Java】可变长度参数
  • 写导出接口的一些理解
  • lazada 商品详情 API 的获取与应用