go并发学习

x_focus · · 2273 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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


2.4)select(监听信道)
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.770723869 +0800 CST
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

       
未完待续

有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:x_focus

查看原文:go并发学习

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2273 次点击  
加入收藏 微博
1 回复  |  直到 2000-01-01 00:00:00
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传