go极简的25个关键字很给力,让程序员把更多的精力投入到业务逻辑上;反观C++各种奇技无穷仍让你多年后还在感叹它的魔力。
</pre><strong><span style="font-size:18px">概念:</span></strong><p></p><p> 并发):1、多个执行实例在单个CPU上交替进行,在逻辑上表现为同一时刻发生,但在物理上是串行的。</p><p> 并行):1、多个执行实例在多个CPU上并行执行。</p><p> 可以这么理解,并发是一种程序算法实现在单个CPU上模拟同一时刻发生的手段,而并行是物理状态同时发生的。</p><p> <span style="font-size:10px">GO使用CSP(<span style="font-family:微软雅黑,宋体; letter-spacing:0.5px; line-height:25px; background-color:rgb(250,250,250)">Communicating Sequential Processes)<span style="font-family:宋体; line-height:26px">通信顺序进程用来描述并发系统中的交互模式。</span></span></span></p><p><span style="font-family:微软雅黑,宋体; letter-spacing:0.5px; line-height:25px; background-color:rgb(250,250,250)"><span style="font-family:宋体; line-height:26px"><span style="font-size:10px"> 更多并发模型可以参考<a target=_blank target="_blank" href="http://blog.csdn.net/sunqihui/article/details/5959576#_Toc275504198">点击打开链接</a></span></span></span></p><p><span style="font-family:微软雅黑,宋体; font-size:14px; letter-spacing:0.5px; line-height:25px; background-color:rgb(250,250,250)"></span></p><p style="color:rgb(51,51,51); font-family:Arial; font-size:14px; line-height:26px"><strong> 进程、线程、协程的关系和区别:</strong></p><p style="color:rgb(51,51,51); font-family:Arial; font-size:14px; line-height:26px"><strong> 1)</strong>进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。</p><p style="color:rgb(51,51,51); font-family:Arial; font-size:14px; line-height:26px"> 2)线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。</p><p style="color:rgb(51,51,51); font-family:Arial; font-size:14px; line-height:26px"><span style="white-space:pre"></span>3)协程(系统不提供)和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。</p><p><strong><span style="font-size:18px">关键字:</span></strong></p><p> <span style="">go(协程并发执行的关键字), chan(协程同步和通信的关键字), select(监听信道)</span></p><p><span style="font-family:Arial; color:#666666"><span style=""><strong></strong></span></span></p><p><span style="font-family:Arial; font-size:14px"><span style=""><strong>1)GO(并发执行)关键字</strong></span></span></p><p><span style="font-family:Arial"><span style=""></span></span></p><p><strong><span style="font-family:Arial"><span style="">1.1) 传统顺序结构下的程序</span></span></strong></p><pre name="code" class="java">package main import ( "fmt" "strconv" "time" ) func output(str string, sec time.Duration) { time.Sleep(sec * time.Second) fmt.Println(str, time.Now()) } func main() { startTime := time.Now() for i := 0; i < 5; i++ { output(strconv.Itoa(i), 2) } time.Sleep(5 * time.Second) fmt.Println(time.Now().Sub(startTime)) }
输出如下:
0 2014-12-24 17:38:36.46757138 +0800 CST
1 2014-12-24 17:38:38.47227465 +0800 CST
2 2014-12-24 17:38:40.476292438 +0800 CST
3 2014-12-24 17:38:42.478405724 +0800 CST
4 2014-12-24 17:38:44.480628443 +0800 CST
15.019152527s
可以观察这个顺序结构下的所耗时间,很符合预期在5次循环中分别执行一个函数,每个函数休眠2秒,加上最后5秒正好正好是15秒
1.2) 并发模式
package main import ( "fmt" "strconv" "time" ) func output(str string, sec time.Duration) { time.Sleep(sec * time.Second) fmt.Println(str, time.Now()) } func main() { startTime := time.Now() for i := 0; i < 5; i++ { go output(strconv.Itoa(i), 2) } time.Sleep(5 * time.Second) fmt.Println(time.Now().Sub(startTime)) }输出如下:
0 2014-12-24 19:42:15.686426362 +0800 CST1 2014-12-24 19:42:15.686739089 +0800 CST2 2014-12-24 19:42:15.686760774 +0800 CST3 2014-12-24 19:42:15.686771999 +0800 CST4 2014-12-24 19:42:15.686782777 +0800 CST5.00425374s
可以看到总耗时间为5秒精确到纳秒级别下的偏差,注意这里5个并发休眠为2秒小于MAIN主协程所以总共耗时也只有5秒,如果并发休眠时间大于主协程休眠时间那么则主线程退出,也就看不到并发函数的输出了。
1.3)并行模式(并行一定属于并发,但并发未必是并行)
package main import ( "fmt" "strconv" "time" ) func output(str string, sec time.Duration) { time.Sleep(sec * time.Second) for i := 0; i < 5; i++ { fmt.Println(i, time.Now()) } func main() { startTime := time.Now() for i := 0; i < 5; i++ { go output(strconv.Itoa(1), 2) } time.Sleep(5 * time.Second) fmt.Println(time.Now().Sub(startTime)) }
输出如下:
0 2014-12-25 00:28:49.367975048 +0800 CST
1 2014-12-25 00:28:49.368483096 +0800 CST
2 2014-12-25 00:28:49.368488316 +0800 CST
3 2014-12-25 00:28:49.368491763 +0800 CST
4 2014-12-25 00:28:49.368497156 +0800 CST
0 2014-12-25 00:28:49.368199199 +0800 CST
1 2014-12-25 00:28:49.368509244 +0800 CST
2 2014-12-25 00:28:49.368512021 +0800 CST
3 2014-12-25 00:28:49.368515648 +0800 CST
4 2014-12-25 00:28:49.368518219 +0800 CST
0 2014-12-25 00:28:49.368221194 +0800 CST
1 2014-12-25 00:28:49.368524406 +0800 CST
2 2014-12-25 00:28:49.368528476 +0800 CST
3 2014-12-25 00:28:49.368534461 +0800 CST
4 2014-12-25 00:28:49.368544931 +0800 CST
0 2014-12-25 00:28:49.368230788 +0800 CST
1 2014-12-25 00:28:49.368583808 +0800 CST
2 2014-12-25 00:28:49.368589128 +0800 CST
3 2014-12-25 00:28:49.368593184 +0800 CST
4 2014-12-25 00:28:49.368597326 +0800 CST
0 2014-12-25 00:28:49.368238338 +0800 CST
1 2014-12-25 00:28:49.368606783 +0800 CST
2 2014-12-25 00:28:49.368610953 +0800 CST
3 2014-12-25 00:28:49.368614906 +0800 CST
4 2014-12-25 00:28:49.368619518 +0800 CST
5.003777317s
可以看它在同一时刻发生是并发没有错,但回到上面的概念与并行不同,并行是多个实例在多个CPU执行,那么我们一般PC电脑或者笔记本都只有一个CPU,怎么能模拟并行的场景呢,这里就要了解一下物理CPU、核数、逻辑CPU的关系。现在的CPU大多是双核之上了,这就意味着通过划分任务和线程可以在多核上实现并行了,可以这么理解多核心对操作系统而言就是多个物理CPU,在有些场景下逻辑CPU与核数对应不上有可能CPU使用了超线程技术。
改更上面的例子,使用调度器模拟并行
package main import ( "fmt" "runtime" "strconv" "time" ) func output(str string, sec time.Duration) { time.Sleep(sec * time.Second) for i := 0; i < 5; i++ { fmt.Println(i, time.Now()) } } func main() { runtime.GOMAXPROCS(2) // 使用2个核 startTime := time.Now() for i := 0; i < 5; i++ { go output(strconv.Itoa(1), 2) } time.Sleep(5 * time.Second) fmt.Println(time.Now().Sub(startTime)) }输出如下:
0 2014-12-25 00:35:57.594041647 +0800 CST
0 2014-12-25 00:35:57.594219738 +0800 CST
1 2014-12-25 00:35:57.59447752 +0800 CST
1 2014-12-25 00:35:57.594476942 +0800 CST
2 2014-12-25 00:35:57.594483738 +0800 CST
2 2014-12-25 00:35:57.594484792 +0800 CST
3 2014-12-25 00:35:57.594487043 +0800 CST
3 2014-12-25 00:35:57.594492282 +0800 CST
4 2014-12-25 00:35:57.59449858 +0800 CST
4 2014-12-25 00:35:57.59449769 +0800 CST
0 2014-12-25 00:35:57.593993358 +0800 CST
0 2014-12-25 00:35:57.594344213 +0800 CST
1 2014-12-25 00:35:57.5945118 +0800 CST
1 2014-12-25 00:35:57.594515977 +0800 CST
2 2014-12-25 00:35:57.594517485 +0800 CST
2 2014-12-25 00:35:57.594520762 +0800 CST
3 2014-12-25 00:35:57.594528518 +0800 CST
3 2014-12-25 00:35:57.594522068 +0800 CST
4 2014-12-25 00:35:57.594533988 +0800 CST
0 2014-12-25 00:35:57.593980927 +0800 CST
4 2014-12-25 00:35:57.594537845 +0800 CST
1 2014-12-25 00:35:57.594543532 +0800 CST
2 2014-12-25 00:35:57.594548405 +0800 CST
3 2014-12-25 00:35:57.5945527 +0800 CST
4 2014-12-25 00:35:57.594556745 +0800 CST
5.004689835s
2)chan (信号通道)关键字
chan map slice 使用make初始化,通道为分无缓冲与缓冲方式,它们的区别是无缓冲阻塞当前协程,默认为无缓冲的。
2.1)无缓冲通道
package main import ( "fmt" "strconv" "time" ) var ch chan int = make(chan int) func output(str string, sec time.Duration) { time.Sleep(sec * time.Second) fmt.Println(str, time.Now()) ch <- 1 //向通道发送数据,如果没有其它协程来取,则当前协程阻塞 } func main() { startTime := time.Now() go output(strconv.Itoa(1), 2) <-ch // 从通道读取数据丢弃,如果通道未有数据则阻塞当前协程,这样main主协程就不会因为并发还没有执行完而退出了 fmt.Println(time.Now().Sub(startTime)) }输出如下:
1 2014-12-25 00:05:48.612852729 +0800 CST
2.014814871s
2.2)死锁状态
package main var ch chan int = make(chan int) func main() { ch <- 1 <-ch }输出如下:
fatal error: all goroutines are asleep - deadlock!
如果从通道中读取数据,通道中未有数据则阻塞当前协程; 向通道流入数据,如果没有其它协程来拿走同样阻塞当前协程。 可见无缓冲通道在一个协程下会发生死锁因为没有其它协程来读/取数据。
2.3)有缓冲通道
更改上面死锁的例子
package main var ch chan int = make(chan int, 1) func main() { ch <- 1 <-ch }如果通道分配了容量并且没有满的状态下当前不会产生阻塞
<pre name="code" class="java">package main var ch chan int = make(chan int, 1) func main() { ch <- 1 ch <- 2 }
由于分配1个容量,流入2个数据超出容量,发生死锁
package main import ( "fmt" "runtime" "strconv" "time" ) var ch chan int = make(chan int) func output(str string, sec time.Duration) { time.Sleep(sec * time.Second) for i := 0; i < 5; i++ { fmt.Println(i, time.Now()) } ch <- 0 } func main() { runtime.GOMAXPROCS(2) // 使用2个核 startTime := time.Now() for i := 0; i < 5; i++ { go output(strconv.Itoa(1), 2) } for i := 0; i < 5; i++ { <-ch } fmt.Println(time.Now().Sub(startTime)) }
输出如下:
0 2014-12-25 01:21:28.530587866 +0800 CST
1 2014-12-25 01:21:28.540051344 +0800 CST
2 2014-12-25 01:21:28.5400612 +0800 CST
3 2014-12-25 01:21:28.54006584 +0800 CST
4 2014-12-25 01:21:28.540069734 +0800 CST
0 2014-12-25 01:21:28.530554048 +0800 CST
1 2014-12-25 01:21:28.540087008 +0800 CST
2 2014-12-25 01:21:28.540093445 +0800 CST
0 2014-12-25 01:21:28.530548904 +0800 CST
3 2014-12-25 01:21:28.540097701 +0800 CST
4 2014-12-25 01:21:28.540101064 +0800 CST
1 2014-12-25 01:21:28.540101514 +0800 CST
0 2014-12-25 01:21:28.530584385 +0800 CST
2 2014-12-25 01:21:28.540123409 +0800 CST
1 2014-12-25 01:21:28.540125945 +0800 CST
3 2014-12-25 01:21:28.540128325 +0800 CST
2 2014-12-25 01:21:28.540130956 +0800 CST
4 2014-12-25 01:21:28.540135045 +0800 CST
3 2014-12-25 01:21:28.540135284 +0800 CST
0 2014-12-25 01:21:28.530586666 +0800 CST
4 2014-12-25 01:21:28.540146074 +0800 CST
1 2014-12-25 01:21:28.54014817 +0800 CST
2 2014-12-25 01:21:28.540152728 +0800 CST
3 2014-12-25 01:21:28.54015693 +0800 CST
4 2014-12-25 01:21:28.540161064 +0800 CST
2.010080495s
package main import ( "fmt" "runtime" "strconv" "time" ) var ch chan int = make(chan int) func output(str string, sec time.Duration, data int) { time.Sleep(sec * time.Second) for i := 0; i < 5; i++ { fmt.Println(i, time.Now()) } ch <- data } func main() { timeout := time.After(3 * time.Second) runtime.GOMAXPROCS(2) // 使用2个核 startTime := time.Now() for i := 0; i < 5; i++ { go output(strconv.Itoa(1), 2, i) } for t := false; !t; { select { case c := <-ch: fmt.Println(c) case <-timeout: // 超时处理 t = true } } fmt.Println(time.Now().Sub(startTime)) }
输出如下:
0 2014-12-25 11:52:26.770760593 +0800 CST
1 2014-12-25 11:52:26.771182791 +0800 CST
1 2014-12-25 11:52:26.77118497 +0800 CST
2 2014-12-25 11:52:26.771188904 +0800 CST
2 2014-12-25 11:52:26.77119193 +0800 CST
3 2014-12-25 11:52:26.771198361 +0800 CST
3 2014-12-25 11:52:26.771192788 +0800 CST
4 2014-12-25 11:52:26.771201893 +0800 CST
4 2014-12-25 11:52:26.771203326 +0800 CST
0 2014-12-25 11:52:26.771001585 +0800 CST
3
2
1 2014-12-25 11:52:26.771216479 +0800 CST
2 2014-12-25 11:52:26.771220719 +0800 CST
0 2014-12-25 11:52:26.770724008 +0800 CST
3 2014-12-25 11:52:26.771224156 +0800 CST
4 2014-12-25 11:52:26.771228775 +0800 CST
1 2014-12-25 11:52:26.771228728 +0800 CST
0
2 2014-12-25 11:52:26.771233756 +0800 CST
3 2014-12-25 11:52:26.771238263 +0800 CST
0 2014-12-25 11:52:26.770912996 +0800 CST
4 2014-12-25 11:52:26.77124198 +0800 CST
1 2014-12-25 11:52:26.771243485 +0800 CST
4
2 2014-12-25 11:52:26.77124763 +0800 CST
3 2014-12-25 11:52:26.771251824 +0800 CST
4 2014-12-25 11:52:26.771255369 +0800 CST
1
3.004506528s