第四章、并发编程

一、并发基础

  1. 并发的应用场景:

    1. 图形用户界面

    2. Web服务

    3. "事务处在分布式环境上,相同的工作单元在不同的计算机上处理着被分片的数据",不理解!!!

    4. CPU从单内核向内核发展

    5. 阻塞的IO操作

  2. 并发的优点:

    1. 更客观的表现问题模型

    2. 充分利用CPU的多内核

    3. 充分利用CPU与其他硬件设备固有的异步性

  3. 并发的实现模型:

    1. 多进程:

      1. 操作系统层面的并发;

      2. 开销大

    2. 多线程:

      1. 操作系统层面的并发;

      2. 开销较多进程小,但依然较大

    3. 基于回调的非阻塞/异步IO:

      1. 在Node.js中有较好的实践

      2. 编程比多线程复杂

    4. 协程:

      1. 用户态层面的并发;

        1. 关于用户态层面的并发的理解可参考:goroutine背后的系统知识:http://www.sizeofvoid.net/goroutine-under-the-hood/ 再读三遍!!!

      2. 可以在语言层面支持,或者通过库支持

      3. 开销极小,编程简单,结构清晰;

二、协程

  1. “可轻松创建上百万个协程”协程也需要上下文环境的保存,其上下文环境比系统线程要小很多吗???

  2. 语言层面支持协程的优点:

    1. Go语言标准库提供的所有系统调用操作(包括同步IO操作),都会让出CPU给其他goroutine。

    2. 上述“系统调用操作”存在于哪些包中???

  3. 库层面支持协程的缺点:

    1. 与语言层面的优点相对应:一个协程调用同步IO操作,其他协程也会阻塞,无法达到期望的目标。

三、goroutine

  1. 在函数调用前加上go关键字,即启动了一个goroutine;

  2. 函数返回,goroutine运行结束;

  3. 函数返回值被忽略;

四、并发通信

  1. 两种并发通信模型:共享数据和消息;

  2. Go使用消息机制进行通信;在goroutine间尽量不共享变量;

  3. 关于channel参考:goroutine和channel的简单理解:http://www.cnblogs.com/yjf512/archive/2012/06/06/2537712.html

五、channel

  1. channel是进程内的多个goroutine间的通信方式;跨进程通信使用Socket或HTTP协议。

  2. channel是类型相关的,一个channel只能传递一种类型的值。

  3. 基本语法:

    1. 声明channel: var chanName chan ElementType,示例如下:

      1. var ch chan int // 声明一个int型的channel;注意此时ch未初始化,值为nil;

      2. var chm map[string] chan bool // 声明一个map,其值为bool型的channel;

      3. charr := make([]chan int, 10)

        1. 声明并初始化了一个包含10个int型channel的数组;

        2. 注意此时数组元素并未初始化,其值为nil;

        3. 每个元素初始化时,仍然需要使用make()函数,示例:charr[0] = make(chan int)

    2. 定义一个channel:使用make函数,示例如下:

      1. ch = make(chan int)

    3. 写入与读取chan:

      1. ch := make(chan int)

      2. 写入:

        1. 语法示例:ch <- 1

        2. 结论:直到有goroutine读取该chan后,否则该写入操作一直阻塞;

      3. 读取:

        1. 语法示例:value := <- ch

        2. 结论:读取操作一直阻塞,直到有goroutine向该chan写入数据;

      4. 注意:以上两点结论,仅针对不带缓冲的channel;

  4. select:

    1. Go在语言级别支持select关键字,用于处理异步IO问题。

    2. 用法示例:

      1. select {

        1. case <- chan1:

          1. // 如果chan1成功读到数据,则进行该case处理语句;

        2. case chan2 <- 1:

          1. // 如果成功向chan2写入数据,则进行该case处理语句;

        3. default:

          1. // 如果上面都没有成功,则进入default处理流程;

      2. }

      3. 注意:每个case语句里必须有一个IO操作。

  5. 缓冲机制:

    1. 创建带缓冲的channel:ch := make(chan int, 10) // make的第二个参数为channel的缓冲区大小。

    2. 缓冲区被填满前,写入操作是不会阻塞的。

    3. 如果单词写入操作比缓冲区大,会阻塞吗???

  6. 超时机制:

    1. go语言没有提供直接的超时处理机制。

      1. value := <- ch,如果一直没有想ch写入数据,会导致该goroutine一直阻塞。

    2. 利用select关键字,实现超时处理。示例如下:

      1. timeout := make(chan bool, 1)

      2. go func() {

        1. time.Sleep(time.Second)

        2. timeout <- true

      3. } () // 定义匿名函数,并在goroutine中运行;

      4. select {

        1. case <- ch:

          1. // other process

        2. case <- timeout:

          1. // 在1秒中内ch中一直没有可读数据时,timeout可读,select执行结束,从而不再去一直读取ch。

      5. }

  7. channel的传递:

    1. channel同int等基本类型一样,也可以通过另一个channel传递给其他goroutine;

  8. 单向channel:

    1. 语法示例:

      1. 声明:

        1. var ch1 chan int // 双向channel

        2. var ch2 chan<- int // 单向只写channel

        3. var ch3 <-chan int // 单向只读channel

      2. 初始化:

        1. ch4 := make(chan int) // 双向channel

        2. ch5 := chan<- int(ch4) // 单向只写channel

        3. ch6 := <-chan int(ch4) // 单向只读channel

    2. 类型转换:

      1. 如上述ch4、ch5和ch6可以将channel在单向和双向之间进行类型转换

    3. 用法示例:

      1. func Parse(ch <-chan int) {

        1. // 一般单向channel用于函数参数中,

        2. // 用来提示该函数实现对参数channel只能进行指定方向的操作。

      2. }

  9. 关闭channel:

    1. 语法示例:close(ch)

    2. 判断channel是否已关闭:

      1. value, ok := <-ch,如果ok为false,则ch已关闭。

六、多核并行化

  1. Go1.0(或Go1.1)版本中,多个goroutine在多核CPU上并发运行时,实际上只在一个核心上运行。

    1. 多个goroutine并发执行时,运行时间较单一执行流是否明显缩短,使用Benchmark测试???

    2. 怎么确定有多少个CPU核心在工作???

    3. 解决方法:

      1. 设置环境变量GOMAXPROCS的值为CPU核心数;

      2. 或者在启动goroutine前调用如下方法:runtime.GOMAXPROCS( NumCPU() )

    4. 确认在Go的哪个版本开始,Go能够智能的利用多核资源???

七、让出时间片

  1. 方法:在goroutine中调用runtime.Gosched()函数,可以是该goroutine主动让出时间片。

  2. 课下需要详细了解runtime包的具体功能。

八、同步

  1. 同步锁:

    1. 互斥锁:sync.Mutex

    2. 读写锁:sync.RWMutex

    3. 注意:

      1. 为避免死锁,可以在获得锁后,在defer关键字中释放锁,然后再对共享资源进行操作。

      2. defer在函数运行结束后才会执行,最好将对共享资源的操作封装在独立的函数中。

  2. 全局唯一性操作:

    1. sync.Once类型:

      1. 保证了全局唯一操作(如:全局初始化)在多个goroutine中,仅被执行一次;

      2. 并且在该全局唯一操作执行结束前,其他goroutine处于阻塞状态;

    2. sync.atomic子包中,提供了一些基础数据类型的原子操作函数,需要多了解。

九、完整示例

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值