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

概要

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

goroutine

关于Goroutine的基本使用

启动多个 Goroutine 打印数字

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

用法提示:

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

答案
package main

import "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 main

import "sync"

func main() {
	var wg sync.WaitGroup

	wg.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) // 创建一个整型 Channel

go func() {
    ch <- 10  // 发送数据到 Channel
}()

value := <-ch  // 从 Channel 接收数据

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

答案
package main

import "sync"

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

	wg.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 的 Channel

ch <- 1  // 向缓冲区发送数据,未被消费时不会阻塞,直到缓冲区满
ch <- 2
ch <- 3

value := <-ch  // 消费数据,缓冲区释放

错误示范
package main

import (
	"fmt"
	"sync"
)

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

	wg.Add(1)
	go func() {
		defer wg.Done()
		var sum int
		for i := 1; i <= 5; i++ {
			sum += i
		}
		ch <- sum
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		var sum int
		for i := 6; i <= 10; i++ {
			sum += i
		}
		ch <- sum
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		var sum int
		for i := 11; i <= 15; i++ {
			sum += i
		}
		ch <- sum
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		var sum int
		for i := 1; i <= 3; i++ {
			sum += <-ch
		}
		fmt.Println(sum)
	}()
	wg.Wait()
	/*
		在最后一个 Goroutine 中,尝试计算所有 Goroutines 传回的和,
		但这个 Goroutine 和前面计算任务的 Goroutines 是同时执行的。
		如果主 Goroutine提前开始读取 Channel 数据,
		其他 Goroutines 还没有来得及发送数据,这就可能导致程序行为不符合预期,
		甚至可能出现死锁
	*/
}

答案
package main

import (
	"fmt"
	"sync"
)

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

	wg.Add(1)
	go func() {
		defer wg.Done()
		var sum int
		for i := 1; i <= 5; i++ {
			sum += i
		}
		ch <- sum
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		var sum int
		for i := 6; i <= 10; i++ {
			sum += i
		}
		ch <- sum
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		var sum int
		for i := 11; i <= 15; i++ {
			sum += i
		}
		ch <- sum
	}()

	go func() {
		wg.Wait()
		close(ch)
	}()

	var ans int
	for 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 main

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

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

	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 1; i <= 20; i++ {
			ch <- i
			time.Sleep(100 * time.Millisecond)
		}
		close(ch)
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			v, ok := <-ch
			time.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 main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

func main() {
	ch := make(chan int, 5)

	var wg sync.WaitGroup

	wg.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) + 1
			time.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 main

import (
	"fmt"
	"sync"
)

func main() {
	// 如果不加锁 结果会是怎么样的
	var mutex sync.Mutex
	var count int
	var wg sync.WaitGroup

	for 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 main

import (
	"fmt"
	"sync"
)

type node struct {
	mutex sync.RWMutex
	count int
}

func main() {

	var c node
	var wg sync.WaitGroup

	for 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()
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值