是否关闭管道 | 是否有缓冲 | 读取次数 ?写入次数 | 现象 |
---|---|---|---|
是 | 是 | > | 读取次数多于部分读到的值为管道数据结构的默认值 |
是 | 是 | < | 读到多少算多少,多的值读不了了 |
是 | 是 | = | 正常读取 |
否 | 是 | > | 死锁 |
否 | 是 | < | 不会死锁,读到多少算多少,多的值读不了了 |
否 | 是 | = | 正常读取 |
//deadlock func deadlockCase() { //无缓冲的信道在取消息和存消息的时候都会挂起当前的goroutine, //而当前只有1个main的线程(也是一个goroutine), //因此在进行存放数据的ch <- 1这一行,就会产生锁,致使后边代码没法执行。 //而整个程序无其它线程来让这个锁释放,天然就造成了一个死锁。 c := make(chan int) c <- 1 close(c) for i := range c { fmt.Println(i) } }
An emptyselect{}
statement blocks indefinitely i.e. forever. It is similar to an emptyfor{}
statement.
On most (all?) supported Go architectures, the empty select will yield CPU. An empty for-loop won't, i.e. it will "spin" on 100% CPU.
package main import ( "fmt" "sync" ) func main() { testSlice := []string{"test1", "test2", "test3"} wg := sync.WaitGroup{} for _, t := range testSlice { wg.Add(1) go printSlice(t, &wg) } wg.Wait() } func printSlice(s string, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("this is %+v\n", s) }
sync包实现并发的核心就是waitgroup,初始化一个WaitGroup类型的指针,须要并发时,则使用Add方法,添加计数,并在须要进行并发的函数中将其做为参数传入,当这个函数操做完成后,调用Done方法减掉计数;在主协程中,Wait方法会一直监听计数器的数量,当计数器为0时,说明全部的并发函数都完成了,这时主协程就能够退出了;我的感受这是最简单的实现并发的方式,在须要并发处理一个集合内的全部数据时尤为好用;
管道是go原生支持的数据类型,使用它也能达到并发的效果
一般的思路是,在主协程取管道中的数据,这时管道会阻塞,在发送协程中向管道里塞数据,塞完数据后关闭掉管道;当主协程取不到数据,管道也关闭后,任务就完成了编程
package main import "fmt" var channel = make(chan int, 10) func main() { go func() { for i := 0; i < 10; i++ { channel <- i } close(channel) //放完数据后必定要关闭chan,不然会死锁; }() for v := range channel { fmt.Println(v) } //在主协程从chan里取数据,由于取不到会一直阻塞,这样main routine就不会退出; //若是另起一个协程取数据,在另外一个协程里阻塞,但主协程并未阻塞,取数据协程还没取到,主协程就退出了; }
上面的例子其实并无体现出并发执行,由于十个数按次序塞进管道中,主协程按次序从管道里取出了数据,仍是一个串行的过程
package main import ( "fmt" ) var channel = make(chan int) var result = make(chan int) func main() { go func() { for i := 0; i < 100; i++ { channel <- i } close(channel) }() ct := 0 for c := range channel { go func(i int) { result <- i + i }(c) } for v := range result { ct++ fmt.Println(v) if ct == 100 { break } } }
把数据按次序投入到管道中后,遍历管道,每取出一个数据,则开启一个新的协程来作计算工做(i+i),而后将结果放到result队列中,最后在主协程取出result管道的值;因为须要关闭result管道,可是关闭的位置很差肯定,目前暂时按计算的数据量来决定跳出循环的时机,你们也可讨论下何时应该关闭result管道;注意,关闭管道与监听管道取值须要是两个不一样的协程,若两个操做都在一个协程,要么监听了一个已经关闭的协程,要么监听了一个没有被关闭的协程,都会产生异常数据结构
在这里,管道的容量是0,即每次往管道塞数据须要等里面的数据被消费后才能继续塞;有容量的协程则是能够塞进数量为容量数的数据,以后的数据须要阻塞,直到有空余;并发
先看代码函数
start := time.Now() c := make(chan interface{}) ch1 := make(chan int) ch2 := make(chan int) go func() { time.Sleep(4*time.Second) close(c) }() go func() { time.Sleep(3*time.Second) ch1 <- 3 }() go func() { time.Sleep(3*time.Second) ch2 <- 5 }() fmt.Println("Blocking on read...") select { case <- c: fmt.Printf("Unblocked %v later.\n", time.Since(start)) case <- ch1: fmt.Printf("ch1 case...") case <- ch2: fmt.Printf("ch2 case...") default: fmt.Printf("default go...") }
运行上述代码,因为当前时间还未到3s。因此,目前程序会走default。
若注释掉default,ch1,ch2分支会随机挑选一个执行,若将ch1 ch2的sleep时间改成10秒,则会执行c分支;
即oop
部分参考:https://www.jianshu.com/p/2a1...this