• Golang


    Golang - 并发编程

     

    Golang - 并发编程

    1. 并行和并发

    • 并行:在同一时刻,有多条指令在多个CPU处理器上同时执行
    • 2个队伍,2个窗口,要求硬件支持
    • 并发:在同一时刻,只能有一条指令执行,但多个进程指令被快速地轮换执行
    • 2个队伍,1个窗口,要求提升软件能力

    2. go语言并发优势

    • go从语言层面就支持了并发
    • 简化了并发程序的编写

    3. goroutine是什么

    • 它是go并发设计的核心
    • goroutine就是协程,它比线程更小,十几个goroutine在底层可能就是五六个线程
    • go语言内部实现了goroutine的内存共享,执行goroutine只需极少的栈内存(大概是4~5KB)

    4. 创建goroutine

    • 只需要在语句前添加go关键字,就可以创建并发执行单元

      package main

      import (
      "fmt"
      "time"
      )

      //测试协程
      //循环打印内容
      func newTask() {
      i := 0
      for {
      i++
      fmt.Printf("new goroutine:i=%d ", i)
      time.Sleep(1 * time.Second)
      }
      }

      //main()相当于是主协程
      func main() {
      //启动子协程
      go newTask()
      i := 0
      for {
      i++
      fmt.Printf("main goroutine:i=%d ", i)
      time.Sleep(1 * time.Second)
      }
      }

    • 开发⼈员无需了解任何执⾏细节,调度器会自动将其安排到合适的系统线程上执行
    • 如果主协程退出了,其他任务还执行吗?不执行

        package main
      
        import (
           "fmt"
           "time"
        )
      
        //main()相当于是主协程
        func main() {
           //匿名子协程
           go func() {
              i := 0
              for {
                 i++
                 fmt.Println("子协程 i=", i)
                 time.Sleep(1 * time.Second)
              }
           }()
           i := 0
           for {
              i++
              fmt.Println("主协程 i=", i)
              time.Sleep(1 * time.Second)
              //主协程第二次后退出
              if i == 2 {
                 break
              }
           }
        }
    • 程序没任何输出,也不报错

        package main
      
        import (
           "fmt"
           "time"
        )
      
        //main()相当于是主协程
        func main() {
           //匿名子协程
           go func() {
              i := 0
              for {
                 i++
                 fmt.Println("子协程 i=", i)
                 time.Sleep(1 * time.Second)
              }
           }()
        }

    5. runtime包

    • runtime.Gosched():用于让出CPU时间片,调度器重新安排任务调度,还是有几率分配到它的

        package main
      
        import (
           "fmt"
           "runtime"
        )
      
        func main() {
           //匿名子协程
           go func(s string) {
              for i := 0; i < 2; i++ {
                 fmt.Println(s)
              }
           }("world")
           //主协程
           for i := 0; i < 2; i++ {
              runtime.Gosched()
              fmt.Println("hello")
           }
        }
    • runtime.Goexit():立即终止当前协程

        package main
      
        import (
           "fmt"
           "time"
           "runtime"
        )
      
        func main() {
           //匿名子协程
           go func() {
              defer fmt.Println("A.defer")
              //匿名函数
              func() {
                 defer fmt.Println("B.defer")
                 //此时只有defer执行
                 runtime.Goexit()
                 fmt.Println("B")
              }()
              fmt.Println("A")
           }()
           for {
              time.Sleep(time.Second)
           }
        }
    • runtime.GOMAXPROCS():设置并行计算的CPU核数,返回之前的值

        package main
      
        import (
           "runtime"
           "fmt"
        )
      
        func main() {
           n := runtime.GOMAXPROCS(3)
           fmt.Println("n=%d
      ",n)
           //循环执行2个
           for{
              go fmt.Print(0)
              fmt.Print(1)
           }
        }

    6. channel是什么

    • goroutine运行在相同的地址空间,因此访问共享内存必须做好同步,处理好线程安全问题
    • goroutine奉行通过通信来共享内存,而不是共享内存来通信
    • channel是一个引用类型,用于多个goroutine通讯,其内部实现了同步,确保并发安全

    7. channel的基本使用

    • channel可以用内置make()函数创建

    • 定义一个channel时,也需要定义发送到channel的值的类型

        make(chan 类型)   //无缓冲的通道
        make(chan 类型, 容量) //有缓冲的通道
    • 当 capacity= 0 时,channel 是无缓冲阻塞读写的,当capacity> 0 时,channel 有缓冲、是非阻塞的,直到写满 capacity个元素才阻塞写入

    • channel通过操作符<-来接收和发送数据,发送和接收数据语法:

        channel <- value   //发送value到channel
        <-channel          //接收通道数据,并丢弃
        x := <-channel    //通道取值并赋给x
        x, ok := <-channel //ok是检查通道是否关闭或者是否为空
    • channel基本使用

        package main
      
        import "fmt"
      
        func main() {
           //创建存放int类型的通道
           c := make(chan int)
           //子协程
           go func() {
              defer fmt.Println("子协程结束")
              fmt.Println("子协程正在运行...")
              //将666发送到通道c
              c <- 666
           }()
           //若已取出数据,下面再取会报错
           //<-c
           //主协程取数据
           //从c中取数据
           num := <-c
           fmt.Println("num = ", num)
           fmt.Println("主协程结束")
        }

    8. 无缓冲的channel

    • 无缓冲的通道是指在接收前没有能力保存任何值的通道
    • 无缓冲通道,有可能阻塞

    发送者 -> (通道(有可能有数据阻塞)) -> 接受者

    package main
    
    import (
       "fmt"
       "time"
    )
    
    func main() {
       //创建无缓冲通道
       c := make(chan int, 0)
       //长度和容量
       fmt.Printf("len(c)=%d, cap(c)=%d
    ", len(c), cap(c))
       //子协程存数据
       go func() {
          defer fmt.Println("子协程结束")
          //向通道添加数据
          for i := 0; i < 3; i++ {
             c <- i
             fmt.Printf("子协程正在运行[%d]:len(c)=%d,cap(c)=%d
    ", i, len(c), cap(c))
          }
       }()
    
       time.Sleep(2 * time.Second)
       //主协程取数据
       for i := 0; i < 3; i++ {
          num := <-c
          fmt.Println("num=", num)
       }
       fmt.Println("主协程结束")
    }

    9. 有缓冲的channel

    • 有缓冲的通道是一种在被接收前能存储一个或者多个值的通道

    发送者 -> (通道(数据),(数据)(...)) -> 接受者

    • 上面代码创建时修改容量即可

        //创建有缓存的通道
        c :=make(chan int, 3)

    10. close()

    • 可以通过内置的close()函数关闭channel

        package main
      
        import "fmt"
      
        func main() {
           //创建通道
           c := make(chan int)
           //子协程存数据
           go func() {
              for i := 0; i < 5; i++ {
                 c <- i
              }
              //子协程close
              close(c)
           }()
           //主协程取数据
           for {
              if data, ok := <-c; ok {
                 fmt.Println(data)
              } else {
                 break
              }
           }
           fmt.Println("Finshed")
        }
    • 也可以如下遍历

        for data := range c{
            fnt.Println(data)
        }

    11. 单方向的channel

    • 默认情况下,通道是双向的,也就是,既可以往里面发送数据也可以接收数据

    • go可以定义单方向的通道,也就是只发送数据或者只接收数据,声明如下

      var ch1 chan int //正常的
      var ch2 chan<- float64 //单向的,只用于写float64的数据
      var ch3 <-chan int //单向的,只用于读取int数据

    • 可以将 channel 隐式转换为单向队列,只收或只发,不能将单向 channel 转换为普通channel

    func main() {
    //创建通道
    c := make(chan int, 3)

    //1. 将c准换为只写的通道
    var send <- chan int =c
    
    //2. 将c转为只读的通道
    var recv <- chan int =c
    
    //往send里面写数据
    send < -1
    
    //从recv读数据
    <-recv

    }

    • 单方向的channel有什么用?模拟生产者和消费者

        package main
      
        import "fmt"
      
        //生产者,只写
        func producter(out chan<- int) {
           //关闭资源
           defer close(out)
           for i := 0; i < 5; i++ {
              out <- i
           }
        }
      
        //消费者,只读
        func consumer(in <-chan int) {
           for num := range in {
              fmt.Println(num)
           }
        }
      
        func main() {
           //创建通道
           c := make(chan int)
           //生产者运行,向管道c存数据
           go producter(c)
           //消费者运行
           consumer(c)
           fmt.Println("done")
        }

    12. 定时器

    • Timer:定时,时间到了响应一次

        package main
      
        import (
           "time"
           "fmt"
        )
      
        func main() {
           //1.基本使用
           //创建定时器
           //2秒后,定时器会将一个时间类型值,保存向自己的c
           //timer1 := time.NewTimer(2 * time.Second)
           ////打印当前时间
           //t1 := time.Now()
           //fmt.Printf("t1:%v
      ", t1)
           ////从管道中取出C打印
           //t2 := <-timer1.C
           //fmt.Printf("t2:%v
      ", t2)
      
           //2.Timer只响应一次
           //timer2 := time.NewTimer(time.Second)
           //for {
           // <-timer2.C
           // fmt.Println("时间到")
           //}
      
           //3.通过Timer实现延时的功能
           ////(1)睡眠
           //time.Sleep(2*time.Second)
           //fmt.Println("2秒时间到")
           ////(2)通过定时器
           //timer3 := time.NewTimer(2 * time.Second)
           //<-timer3.C
           //fmt.Println("2秒时间到")
           ////(3)After()
           //<-time.After(2 * time.Second)
           //fmt.Println("2秒时间到")
      
           //4.停止定时器
           //timer4 := time.NewTimer(3 * time.Second)
           ////子协程
           //go func() {
           // <-timer4.C
           // fmt.Println("定时器器时间到,可以打印了")
           //}()
           //stop := timer4.Stop()
           //if stop {
           // fmt.Println("timer4已关闭")
           //}
      
           //5.重置定时器
           timer5 := time.NewTimer(3 * time.Second)
           //定时器改为1秒
           timer5.Reset(1 * time.Second)
           fmt.Println(time.Now())
           fmt.Println(<-timer5.C)
      
           for {
           }
        }
    • Ticker:响应多次

      package main

      import (
      "time"
      "fmt"
      )

      func main() {
      //创建定时器,间隔1秒
      ticker := time.NewTicker(time.Second)

       i := 0
       //子协程
       go func() {
          for {
             <-ticker.C
             fmt.Println(<-ticker.C)
             i++
             fmt.Println("i=", i)
             //停止定时器
             if i == 5 {
                ticker.Stop()
             }
          }
       }()
      
       //死循环
       for {
      
       }

      }

    13. select

    • go语言提供了select关键字,可以监听channel上的数据流动
    • 语法与switch类似,区别是select要求每个case语句里必须是一个IO操作

        select {
        case <-chan1:
           // 如果chan1成功读到数据,则进行该case处理语句
        case chan2 <- 1:
           // 如果成功向chan2写入数据,则进行该case处理语句
        default:
           // 如果上面都没有成功,则进入default处理流程
        }
      
        package main
      
        import (
           "fmt"
        )
      
        func main() {
           //创建数据通道
           int_chan := make(chan int, 1)
           string_chan := make(chan string, 1)
           //创建2个子协程,写数据
           go func() {
              //time.Sleep(2 * time.Second)
              int_chan <- 1
           }()
           go func() {
              string_chan <- "hello"
           }()
           //如果都能匹配到,则随机选择一个去跑
           select {
           case value := <-int_chan:
              fmt.Println("intValue:", value)
           case value := <-string_chan:
              fmt.Println("strValue:", value)
           }
           fmt.Println("finish")
        }

    14. 携程同步锁

    • go中channel实现了同步,确保并发安全,同时也提供了锁的操作方式
    • go中sync包提供了锁相关的支持
    • Mutex:以加锁方式解决并发安全问题

        package main
      
        import (
           "time"
           "fmt"
           "sync"
        )
      
        //账户
        type Account struct {
           money int
           flag sync.Mutex
        }
      
        //模拟银行检测
        func (a *Account)Check()  {
           time.Sleep(time.Second)
        }
      
        //设置账户余额
        func (a *Account)SetAccount(n int)  {
           a.money +=n
        }
      
        //查询账户余额
        func (a *Account)GetAccount() int{
           return a.money
        }
      
        //买东西1
        func (a *Account)Buy1(n int){
           a.flag.Lock()
           if a.money>n{
              //银行检测
              a.Check()
              a.money -=n
           }
           a.flag.Unlock()
        }
      
        //买东西2
        func (a *Account)Buy2(n int){
           a.flag.Lock()
           if a.money>n{
              //银行检测
              a.Check()
              a.money -=n
           }
           a.flag.Unlock()
        }
      
        func main() {
           var account Account
           //设置账户余额
           account.SetAccount(10)
           //2个子协程买东西
           go account.Buy1(6)
           go account.Buy2(5)
           time.Sleep(2 * time.Second)
           fmt.Println(account.GetAccount())
        }
    • sync.WaitGroup:用来等待一组子协程的结束,需要设置等待的个数,每个子协程结束后要调用Done(),最后在主协程中Wait()即可
    • 引入

        package main
      
        import (
           "fmt"
        )
      
        func main() {
           //创建通道
           ch := make(chan int)
           //count表示活动的协程个数
           count := 2
           go func() {
              fmt.Println("子协程1")
              //子协程1执行完成,给通道发送信号
              ch <-1
           }()
           go func() {
              fmt.Println("子协程2")
              ch <-1
           }()
           //time.Sleep(time.Second)
           //从ch中不断读数据
           for range ch{
              count --
              if count == 0{
                 close(ch)
              }
           }
        }
    • go提供了这种解决方案sync.WaitGroup
    • Add():添加计数
    • Done():操作结束时调用,计数减去1
    • Wait():主函数调用,等待所有操作结束


    未完待续...

     
  • 相关阅读:
    登录及注册页面
    多方式登录
    git笔记
    后台主页模块设计
    auth模块迁移后需新增字段
    使用idea构建SpringBoot源码
    Springboot相关面试问题
    Springboot自动加载工具-devtools的理解与使用
    SpringBoot项目的一些简单常用配置
    Java线程池及Executor框架的理解
  • 原文地址:https://www.cnblogs.com/yanghongtao/p/10970399.html
Copyright © 2020-2023  润新知