1、如何有序的从通道取值
第一种 判断ok的值
package day13
import "fmt"
func D131() {
ch1 := make(chan int)
// 开始goroutine将0-100的数据发送到ch1中
go func() {
for i := 0; i < 100; i++ {
ch1 <- i
}
close(ch1)
}()
for {
i, ok := <-ch1 // 通道关闭后再取值ok=false
if !ok {
break
}
fmt.Println(i)
}
}
第二种 使用range(推荐)
package day13
import "fmt"
func D132() {
ch1 := make(chan int)
go func() {
for i := 0; i < 100; i++ {
ch1 <- i
}
close(ch1)
}()
for i := range ch1 { // 通告关闭后会退出for range循环
fmt.Println(i)
}
}
第三种 使用select
package day13
import "fmt"
func D133() {
ch1 := make(chan int)
go func() {
for i := 0; i < 100; i++ {
ch1 <- i
}
close(ch1)
}()
for {
select {
case i, ok := <-ch1:
if ok {
fmt.Println(i)
}
default:
fmt.Println("default")
}
}
}
2、Select 管理通道
Go
语言中的 select
语句是一种用于多路复用通道的机制,它允许在多个通道上等待并处理消息。
使用 select
语句能够更加高效地管理多个通道。
package day13
import (
"fmt"
"time"
)
/*
select {
case <- channel1:
// channel1准备好了
case data := <- channel2:
// channel2准备好了,并且可以读取到数据data
case channel3 <- data:
// channel3准备好了,并且可以往其中写入数据data
default:
// 没有任何channel准备好了
}
*/
func D134() {
ch := make(chan int)
go func() {
time.Sleep(time.Second)
ch <- 1
}()
select {
case data, ok := <-ch:
if ok {
fmt.Println("received data: ", data)
} else {
fmt.Println("closed channel")
}
case <-time.After(2 * time.Second):
fmt.Println("timeout!!!")
}
}
3. 锁
有时候在Go代码中可能会存在多个goroutine同时操作一个资源(临界区),这种情况会发生竞态问题(数据竞态)。类比现实生活中的例子有十字路口被各个方向的的汽车竞争;还有火车上的卫生间被车厢里的人竞争。
举一个例子
package day13
import (
"fmt"
"sync"
)
var x int
var wg sync.WaitGroup // WaitGroup 用于等待所有 goroutine 完成
func add() {
for i := 0; i < 100; i++ {
x++
}
wg.Done() // 通知 WaitGroup 完成一个 goroutine
}
func minus() {
for i := 0; i < 100; i++ {
x--
}
wg.Done() // 通知 WaitGroup 完成一个 goroutine
}
func D135() {
// 存在资源竟态的例子
wg.Add(2) // 向 WaitGroup 添加两个等待的 goroutine
go add() // 启动一个 goroutine 执行 add 函数
go minus() // 启动另一个 goroutine 执行 minus 函数
wg.Wait() // 等待所有 goroutine 完成
fmt.Println(x)
}
出现了和预期结果不一样的数据!!!!!
上面的代码中我们开启了两个goroutine去累加变量x的值
,这两个goroutine在访问和修改x变量的时候就会存在数据竞争,导致最后的结果与期待的不符
。
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。Go语言中使用sync包的Mutex类型来实现互斥锁。
package day13
import (
"fmt"
"sync"
)
/*
sync 的包的Mutex来实现互斥锁 ------ 操作系统也是用mutex来表示锁变量
*/
var x1 int64
var wg1 sync.WaitGroup
var lock sync.Mutex
func add_lock() {
for i := 0; i < 1000; i++ {
lock.Lock()
x1 += 1
lock.Unlock()
}
wg1.Done()
}
func D136() {
wg1.Add(2)
go add_lock()
go add_lock()
wg1.Wait()
fmt.Println(x1)
}
使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待锁;当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的。
互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。
读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。
package day13
import (
"fmt"
"sync"
"time"
)
var (
x2 int64
wg2 sync.WaitGroup
rwlock sync.RWMutex
)
func write() {
rwlock.Lock() // 加写锁
x2++
time.Sleep(10 * time.Microsecond) // 假设写操作耗时10毫秒
rwlock.Unlock() // 解写锁
wg2.Done()
}
func read() {
rwlock.RLock()
time.Sleep(time.Millisecond)
fmt.Println(x2)
rwlock.RUnlock()
wg2.Done()
}
func D137() {
start := time.Now()
for i := 0; i < 10; i++ {
wg2.Add(1)
go write()
}
for i := 0; i < 1000; i++ {
wg2.Add(1)
go read()
}
wg2.Wait()
end := time.Now()
fmt.Println(end.Sub(start), "x :", x2)
}