并发编程

学习来源:
Go语言实战、blibli 黑马程序员 20小时快速入门go语言(中)

Go语言的优势

  • Go语言设计简单
  • Go语言从语言层面就支持并发。 btw,并发程序的内存管理有时候是非常复杂的,而Go语言提供了自动垃圾回收机制。

Go语言为并发编程而内置的上层API基于CSP(communicating sequentical process, 顺序通信)模型。

一、goroutine

1.1 goroutine

goroutine是Go的协程,是比线程更小的单位。(可以理解成“一个协程对应一个任务”)
goroutine是Go并发设计的核心。goroutine比thread更易用、更高效、更轻便。

func main( )  {
      go newTask( )  //新建一个协程,新建一个任务
                               //该新建的协程可以与 主协程/任务 同时执行
      for {
              fmt.Println("this is a main goroutine.")
              time.Sleep(time.Second) //延时1s
            }
}

新建协程的方法:go+函数
任务调度器自动调度,可使得两个任务同时并发进行。
主协程和子协程执行的先后顺序主要与调度性能有关(可能会随机)

当主协程退出后,其他子协程也会跟着退出。

  • 主协程先退出导致子协程没有来得及调用的例子如下:
package main
import(
            "fmt"
            "time"
           )

//主协程退出了,其他子协程也要跟着退出
func main( )   {
          go fun( )  {  //匿名函数
                        i := 0
                        for {
                              i++
                              fmt.Println("子协程 i = " , i)
                              time.Sleep(time.Second)
                              }
                        }  ( )    //!!!别忘了  ( )
      }

1.2 runtime包

1.2.1 Gosched
runtime.Gosched( ) 用于让出CPU时间片,让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。

package main
import (
            "fmt"
            "runtime"
            )

func main( ) {
            go func( ) {
            for i:= 0; i < 5; i++ {
            fmt.Println("go")
            }
     }   ( )

for i:= 0; i < 2; i++ {
// 让出时间片,先让别的协议执行,它执行完,再回来执行此协程。
runtime.Gosched( )
fmt.Println("hello")
}

未加runtime.Gosched( ) 之前,执行结果只会输出hello,但是加了runtime.Gosched( ) 后,执行结果不仅有hello,还会有go。

1.2.2 Goexit
runtime.Goexit( ) 的作用是终止所在的协程

package main
import (
             "fmt"
             "runtime"
          )

func test( ) {
                  defer fmt.Println("ccc")

                  runtime.Goexit( ) //终止所在的协程
                            
                  fmt.Println("ddd")
                 }

func main( ) {
                    go func( )  { //创建新的协程
                          fmt.Println("aaa")
                           test( )
                          fmt.Println("bbb")
                   } ( )  //别忘了( )

//特地写了一个死循环,目的是不让主协程结束
for {  }

}

//执行结果: aaa  ccc

1.2.3 GOMAXPROCS
调用runtime.GOMAXPROCS( ) 用来设置可以并行计算的CPU核数的最大值,并返回之前的值。
多核的好处是使得协程之间的交替频率变大

n := runtime.GOMAXPROCS( ) 
//指定以单核运算,并返回指定运算前的CPU的核数,赋给n。

二、channel

channel 的存在是为了避免协程与协程交替进行而导致信息混乱的结果。

2.1 channel概述

通过channel实现同步

// 举个打印机的例子
package main
import (
             "fmt"
             "time"
          )

// 全局变量,创建一个channel
var ch = make(chan int)

//定义一个打印机,参数为字符串,按每个字符打印
//打印机属于公共资源
func Println(str string) {
                  for _, data := range str {
                            fmt.Println("%c", data)
                            time.Sleep(time.Second)
                           }
                        fmt.Println("\n")
                    }

//person1执行完后才能到person2执行
func person1( ) {
                  Printer("hello")
                  ch <- 666  //给管道写入666(int型)数据
              }

func person2( ) {
                  <-ch //从管道取数据。接受。   如果通道没有数据就会阻塞。
                  Printer(“world”)
}

func main( ) {
                    // 新建2个协程,代表2个人,2个人同时使用打印机
                    go person1( )
                    go person2( ) 

                   //特地不让主协程结束,死循环。
                   for { }
                  }

                            

2.2 无缓冲的channel

ch<- 往管道 ch 写入数据

创建格式:
make (chan Type) //等价于 make (chan Type, 0)

协程1是把数据写入通道中去后,如果对方没有把数据取走之前,是会阻塞的。

eg: ch := make(chan int, 0)
// len(ch) = 0, cap(ch) = 0

2.3 有缓冲的channel

创建一个有缓冲的channel

ch := make(chan int, 3)  // len(ch) = 0, cap(ch) =3

如果给定了一个缓冲区容量,通道就是异步的。
只要缓冲区有未使用空间用于发送数据,或还包含可以接受的数据,那么其通信就会无阻塞的进行。

2.4 range和close

关闭channel

package main
import("fmt")
func main( )  {
                      ch := make(chan int) //创建一个无缓冲的channel
                      //新建一个goroutine
                      go func( )  {
                           for i:= 0; i < 5; i++ {
                                                ch <- i ; //往通道写数据
                                              }
                      // 不需要再写数据时,关闭channel
                      close(ch)   // 若注释掉 close(ch) ,程序会一直阻塞在此行。
                    }   ( )    //别忘了 ( )

for {
      // 如果OK为true,说明管道没有关闭。
     //如果为false,说明管道已经关闭。
    if num, OK := <-ch; OK == true {
          fmt.Println("num = " , num)
        }   else { //管道关闭
            break
          }
  }
}

btw,关闭channel后无法再发送数据,但是可以继续读数据。

2.5 单方向的channel

//创建一个channel,双向的
ch := make(chan, int)

//双向 channel 能隐式转换为单向 channel
var writeCh chan <- int = ch //只能写,不能读
writeCh <- 666 //写数据

var readCh <- chan int = ch //只能读,不能写
<- readCh

btw, 单向无法转换为双向

var ch2 chan int = writech // 会报错

单向channel的应用

package main
import ( "fmt")

//此通道只能写,不能读
func producer(out chan <- int) {
        for i:= 0; i < 10; i++ {
                      out <-  i*i
                  }
                close(out) //关闭通道后只能读,不能写
}

//此 channel 只能读,不能写
func consumer(in <- chan int) {
        for num := range in {
                      fmt.Println("num = ", num)
                    }
   }

func main( ) {
//创建一个双向通道
ch := make(chan int)

//生产者 生产数字,写入channel
//新开一个协程
go producer (ch)  //channel 传参,引用传递

//消费者,从channel读取内容后,打印
consumer(ch)
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 并发编程基本概念 学习并发编程之前我们需要脑补几个基础知识和思考一个问题什么是串行?什么是并行?什么是并发?什么是...
    极客江南阅读 5,317评论 3 9
  • 参考《快学 Go 语言》第 11 课 —— 千军万马跑协程《快学 Go 语言》第 12 课 —— 通道let's ...
    合肥黑阅读 7,959评论 2 7
  • Go 并发编程 选择 Go 编程的原因可能是看中它简单且强大,那么你其实可以选择C语言;除此之外,我看中 Go 的...
    PRE_ZHY阅读 4,379评论 1 6
  • 概述 简而言之,所谓并发编程是指在一台处理器上“同时”处理多个任务。 随着硬件的发展,并发程序变得越来越重要。We...
    泡泡龙吐泡泡阅读 12,388评论 1 12
  • 并发基础 在说Golang的并发编程之前,先认识一下目前并发的几种实现方式: 1.多进程。操作系统实现的并发模型,...
    睡着别叫醒我阅读 6,671评论 0 1