并行与并发
理论式的概念:
并行:多件事在同一时刻发生。
并发:多件事在同一时间间隔发生。
5岁小孩都能看懂的解释:
摘自:http://www.cnblogs.com/yangecnu/p/3164167.html 和 Concurrent and Parallel Programming
上文如果用程序员的语言来讲,CPU处理器相当于上图的咖啡机的角色,任务相当于队列中的人。
并发与并行的区别:
一定要仔细阅读此文:http://blog.csdn.net/coolmeme/article/details/9997609 。这篇文章提到了网络服务器并发连接数、吐吞量、宽带的概念,对于初学者应该很受用。
Goruntine
goruntine原理
我们知道Go从语言层面就支持了并发,而goruntine是go并发设计的核心。goruntine说到底是协程【Go Web 编程里是线程,也是对的,因为协程类似于用户态线程】。具体原理实现参考:
1. 以goroutine为例看协程的相关概念
2. goroutine与调度器
3. 廖雪峰:协程
4. 知乎:协程的好处是什么?
5. 知乎:golang的goroutine是如何实现的?
这些参考文章建议读者好好看看。
了解了协程、goruntine的实现机制,接下来学习如何启动goruntine。
启动goruntine
goroutine 通过关键字 go 就启动了一个 goroutine。
go hello(a, b, c)//普通函数前加go
例子:
package main
import (
"fmt"
"runtime"
)
func say(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched() //表示让cpu将控制权给其他人
fmt.Println(s)
}
}
func main() {
runtime.GOMAXPROCS(1)
go say("world")
say("hello")
}
输出:
hello
world
hello
world
hello
world
hello
world
hello
很简单,在函数前加一个go关键词就启动了goruntine。
Channel
channel是什么
channel是一种通信通道,goruntine之间的数据通信通过channel来实现。goruntine通过channel发送或者接收消息。
channel的基本操作语法:
cl := make(chan int) //创建一个无缓冲的int型channel,可以根据需求创建bool、string等类型的channel
c1 := make(chan int, 4) //创建有缓冲的int型channel
cl <- x //发送x到channel cl
x := <- cl //从cl中接收数据,并赋值给x
无缓冲的例子:
package main
import (
"fmt"
"time"
)
func sendChan(cl chan string) {
fmt.Println("[send_start]")
cl <- "hello world" // 向cl中加数据,如果没有其他goroutine来取走这个数据,那么挂起sendChan, 直到getChan函数把"hello world"这个数据拿走
fmt.Println("[send_end]")
}
func getChan(cl chan string) {
fmt.Println("[get_start]")
s := <-cl // 从cl取数据,如果cl中还没放数据,那就挂起getChan线程,直到sendChan函数中放数据为止
fmt.Println("[get_end]" + s)
}
func main() {
cl := make(chan string)
go sendChan(cl)
go getChan(cl)
time.Sleep(time.Second)
}
输出:
[send_start]
[get_start]
[get_end]hello world
[send_end]
上面的例子存在3个goruntine,注意main也在一个goruntine中。如果函数main中没有 time.Sleep(time.Second),你会发现什么输出都不会有,为什么呢?是因为另外两个goruntine还没来得及跑,主函数main就已经退出了。
所以需要让main等一下,time.Sleep(time.Second)就是让main停顿一秒再输出。
无缓冲的channel的接收和发送都是阻塞的,也就是说:
- 数据流入无缓冲信道, 如果没有其他goroutine来拿走这个数据,那么当前线阻塞
- 从无缓冲信道取数据,必须要有数据流进来才可以,否则当前goroutine阻塞
有缓冲的例子:
package main
import (
"fmt"
"time"
)
func sendChan(cl chan int, len int) {
fmt.Println("sendChan_enter")
for i := 0; i < len; i++ {
fmt.Println("# ", i)
cl <- i //cl的存储第4个数据的时候,会阻塞当前goruntine,直到其它goruntine取走一个或多个数据
}
fmt.Println("sendChan_end")
}
func getChan(cl chan int, len int) {
fmt.Println("getChan_enter")
for i := 0; i < len; i++ {
data := <-cl
fmt.Println("$ ", data)//当cl的数据为空时,阻塞当前goruntine,直到新的数据写入cl
}
fmt.Println("getChan_end")
}
func main() {
cl := make(chan int, 3)// 写入3个元素都不会阻塞当前goroutine, 存储个数达到4的时候会阻塞
go sendChan(cl, 10)
go getChan(cl, 5)
time.Sleep(time.Second)
}
输出:
sendChan_enter
# 0
# 1
# 2
# 3
getChan_enter
$ 0
$ 1
$ 2
$ 3
# 4
# 5
# 6
# 7
# 8
$ 4
getChan_end
为什么sendChan_end没有输出?
getChan取完5个数据后,getChan这个goruntine就会挂起,而sendChan线程因为数据填满,无法将剩余的数据写入chanl而挂起,最后因main所在的goruntine超时1秒结束而结束。故而看不到sendChan_end的输出。
- 有缓冲的channel是可以无阻塞的写入,当缓冲填满时,再次写入新的数据时,当前goruntine会发生阻塞,直到其它goruntine从channel中取走一些数据:
- 有缓冲的channel可以无阻塞的获取数据,当数据取空时,再次取新的数据时,当前的goruntine会发生阻塞,直到其它goruntine往channel写入新的数据
close
生产者【发送channel的goruntine】通过关键字 close 函数关闭 channel。关闭 channel 之后就无法再发送任何数据了, 在消费方【接收channel的goruntine】可以通过语法 v, ok := <-ch 测试 channel 是否被关闭。如果 ok 返回 false,那么说明 channel 已经没有任何数据并且已经被关闭。
不过一般用得少,网上关于它的描述也不多。
select
语法结构类似于switch。
select {
case cl<-x:
go语句
case <-cl:
go语句
default: //可选,
go语句
}
- 每个case只能是channel的获取或者写入,不能是其它语句。
- 当每个case都无法执行,如果有default,执行default;如果没有default,当前goruntine阻塞。
- 当多个case都可以执行的时候,随机选出一个执行。
关于select的用法,强烈推荐阅读:【GOLANG】Go语言学习-select用法