Golang - 并发编程 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\n", i)
    time.Sleep(1 * time.Second)
    }
    }

    //main()相当于是主协程
    func main() {
    //启动子协程
    go newTask()
    i := 0
    for {
    i++
    fmt.Printf("main goroutine:i=%d\n", 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",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\n", 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\n", 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\n", t1) ////从管道中取出C打印 //t2 := <-timer1.C //fmt.Printf("t2:%v\n", 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():主函数调用,等待所有操作结束


未完待续...

 

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\n", i)
    time.Sleep(1 * time.Second)
    }
    }

    //main()相当于是主协程
    func main() {
    //启动子协程
    go newTask()
    i := 0
    for {
    i++
    fmt.Printf("main goroutine:i=%d\n", 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",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\n", 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\n", 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\n", t1) ////从管道中取出C打印 //t2 := <-timer1.C //fmt.Printf("t2:%v\n", 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():主函数调用,等待所有操作结束


未完待续...

猜你喜欢

转载自www.cnblogs.com/yanghongtao/p/10970399.html