Go的第15课——锁、channel遍历、select 语句
sync.Mutex 基本 这个协程可以理解为多线程里面的线程。线程在操作临界资源的时候会产生竞争,锁可以避免发生竞争。
使用以下语法定义一个锁变量
lock.Lock()
可以为一个锁进行上锁操作,若锁已被其它协程上锁,则阻塞当前协程直到锁被释放。
lock.Unlock()
可以释放一个锁。
实例 看下面这个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport ( "fmt" "sync" ) var wg sync.WaitGroupvar lock sync.Mutexvar k int var locktest bool = false func add () { defer wg.Done() if locktest { lock.Lock() defer lock.Unlock() } k += 1 } func sub () { defer wg.Done() if locktest { lock.Lock() defer lock.Unlock() } k -= 1 } func main () { for i := 0 ; i < 1000 ; i++ { wg.Add(2 ) go add() go sub() } wg.Wait() fmt.Printf("last value:%v\n" , k) }
这里使用了 WaitGroup
同步协程。很明显如果仅仅只是在 add
和 sub
函数中对全局变量进行加减(locktest=false),那么结果不一定是 ==0
的。而如果加上了锁(locktest=true),那么结果必定为 0。
channel遍历 这个本来应该放 channel 那一节的,不过视频顺序如此,也在此补一下相当于。
channel 有个特性就是,如果 channel 没有缓冲区,那么写的时候会阻塞,直到读的协程到来才会继续运行。如果尝试读没有被写的协程,那么会造成错误,因此给遍历带来了一些困难。所以我们使用 channel 的时候,只要确定了后续不往 channel 中写数据就直接关闭,我们在读关闭的 channel 时会读到默认值并且第二个值 ok
会置为 false
。
for循环遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport "fmt" var ch = make (chan int )func test () { for { if v, ok := <-ch; ok { fmt.Printf("value:%v\n" , v) } else { break } } } func main () { go func () { for i := 0 ; i < 10 ; i++ { ch <- i } close (ch) }() test() }
for range 遍历 这种方式更加简单粗暴了,就把 channel 当成一个切片即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport "fmt" var ch = make (chan int , 5 )func test () { for i := range ch { fmt.Println(i) } } func main () { go func () { for i := 0 ; i < 10 ; i++ { ch <- i } close (ch) }() test() }
select语句
select 是Go中的一个控制结构,类似于switch语句,用于处理异步1o操作。select会监听case语句中channel的读写操作,当case中channel读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。
如果有多个case都可以运行,select 会随机公平地选出一个执行,其他不会执行。
如果没有可运行的 case 语句,且有default 语句,那么就会执行 default 的动作。
如果没有可运行的 case 语句,且没有 default 语句,select 将阻塞,直到某个case 通信可以运行
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package mainimport ( "fmt" "runtime" "time" ) var intch = make (chan int )var stringch = make (chan string )func main () { go func () { intch <- 114514 stringch <- "xia0ji233" }() runtime.Gosched() for { select { case r := <-intch: fmt.Printf("get int value: %v\n" , r) case r := <-stringch: fmt.Printf("get string value: %v\n" , r) default : fmt.Println("no value" ) } time.Sleep(time.Second) } }
它还有一个特性就是,for循环select时,如果其中一个case通道已经关闭,则每次都会执行到这个case,有多个 case 被关闭的话则也是随机从被关闭的 case 读取。