您的位置:首页 > 编程语言 > Go语言

Go语言基础:并发

2016-09-21 08:52 387 查看

并行与并发

理论式的概念:

并行:多件事在同一时刻发生。

并发:多件事在同一时间间隔发生。

5岁小孩都能看懂的解释:



摘自:http://www.cnblogs.com/yangecnu/p/3164167.htmlConcurrent 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用法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息