Go语言基础:并发

并行与并发

理论式的概念:

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

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用法

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值