golang 并发

这篇具有很好参考价值的文章主要介绍了golang 并发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Golang 并发

  1. 并行 指的是在同一时刻 有多条指令在多个CPU处理器上同时执行 2个任务2个窗口需要硬件支持
  2. 并发是指在同一时刻 只能有一条指令 单多个进程指令快速轮换执行 2个队伍1个窗口 要求提升软件能力

Golang 并发优势

  • go 从底层就支持并发
  • 简化了并发程序的编写方法

Goroutine 是什么

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

创建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)
    }
    }()
    }

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)
    }
    }

channel

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

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("主协程结束")
    }

无缓冲的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("主协程结束")
}

有缓冲channel

  • 有缓冲的通道是一种在被接受前能存储一个或着多个值的通道 发送者->(通道(数据),(数据)(...))->接受者 在创建时修改容量即可

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

关闭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)
      }

单方向channel

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

  • go 可以定义单方向通道,也就是只发送数据或者只接收数据
    var ch1 chan int //正常的
    var ch2 chan <- float64 //单向的 只用于写float64的数据
    var ch3 <-chan int //单向的, 只用于读取int 数据

  • 可以将channel 隐式转换为单向队列,只收或只发,不能讲单向channel 转为普通channel文章来源地址https://www.toymoban.com/news/detail-776870.html

//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")
    }

定时器

  • 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 {
     }
  }

*   响应多次
    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 {

    }

select 用法

  • go 语言提供了select 关键字,可以监听channel 上的数据流动
  • 语法与switch 类似,区别时select 要求每个case 语句必须是一个IO操作
  select {
  case <-chan1:
     // 如果chan1成功读到数据,则进行该case处理语句
  case chan2 <- 1:
     // 如果成功向chan2写入数据,则进行该case处理语句
  default:
     // 如果上面都没有成功,则进入default处理流程
  }
  ```go
  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")
  }

协程同步锁

  • 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 并发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Golang之路---04 并发编程——WaitGroup

    为了保证 main goroutine 在所有的 goroutine 都执行完毕后再退出,前面使用了 time.Sleep 这种简单的方式。 由于写的 demo 都是比较简单的, sleep 个 1 秒,我们主观上认为是够用的。 但在实际开发中,开发人员是无法预知,所有的 goroutine 需要多长的时间才能执行完毕,sleep 多了,

    2024年02月14日
    浏览(26)
  • Golang扫盲式学习——GO并发 | (一)

    并行:同一个时间段内多个任务同时在不同的CPU核心上执行。强调同一时刻多个任务之间的” 同时执行 “。 并发:同一个时间段内多个任务都在进展。强调多个任务间的” 交替执行 “。 随着硬件水平的提高,现在的终端主机都是多个CPU,每个CPU都是多核结构。当多个CPU同

    2024年02月07日
    浏览(37)
  • Golang 的 GMP:并发编程的艺术

    在 Golang 的并发编程中,GMP 是一个重要的概念,它代表了 Goroutine、M(线程)和 P(调度器)。这个强大的三位一体的并发模型使得 Golang 在处理并发任务时非常高效和灵活。通过 GMP 的组合,Golang 实现了一种高效的并发模型。它充分利用了多核处理器的优势,并通过轻量级的

    2024年02月07日
    浏览(33)
  • Golang Channel详解:安全并发通信与避免死锁方法

    深入了解Golang中的Channel,探讨其线程安全性、类型特性以及避免死锁的方法。学习如何正确初始化、存取数据,关闭Channel以及处理只读只写情况。

    2024年02月10日
    浏览(41)
  • Golang 中的 slice 为什么是并发不安全的?

      在Go语言中,slice是并发不安全的,主要有以下两个原因:数据竞争、内存重分配。   数据竞争:slice底层的结构体包含一个指向底层数组的指针和该数组的长度,当多个协程并发访问同一个slice时,有可能会出现数据竞争的问题。例如,一个协程在修改slice的长度,而

    2024年02月05日
    浏览(47)
  • 深度探讨 Golang 中并发发送 HTTP 请求的最佳技术

    💂 个人网站:【 海拥】【神级代码资源网站】【办公神器】 🤟 基于Web端打造的:👉轻量化工具创作平台 💅 想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】 在 Golang 领域,并发发送 HTTP 请求是优化 Web 应用程序的一项重要技能。本文探讨了实现此目的的各种方法

    2024年01月18日
    浏览(45)
  • Golang 中的 map 为什么是并发不安全的?

      golang 中的 map 是并发不安全的,多个 go 协程同时对同一个 map 进行读写操作时,会导致数据竞争(data race)问题,程序会 panic。   如果一个协程正在写入 map,而另一个协程正在读取或写入 map,那么就有可能出现一些未定义的行为,例如:读取到的值可能是过期的、不

    2024年02月05日
    浏览(50)
  • 006 Golang-channel-practice 并发打印字符串

    使用两个goroutine,向标准输出中按顺序交替打印出字母和数字,输出是a1b2c3... 这里我们使用两个channel保证它们打印的顺序。 使用两个通道(channel)是确保打印顺序的一个有效方法,尤其是在涉及到交替执行的并发任务时。在我们的场景中,目标是让两个 goroutine 交替执行并打

    2024年01月18日
    浏览(33)
  • Golang单元测试与Goroutine详解 | 并发、MPG模式及CPU利用

    深入探讨Golang中单元测试方法及Goroutine的使用。了解并发与并行概念,MPG模式以及CPU相关函数的应用。解决协程并行中的资源竞争问题。

    2024年02月10日
    浏览(36)
  • golang发送get请求的各种操作:自定义header请求头、带cookie请求、channel并发请求

    请求参数放到url.Values{} 接收文件–ioutil.WriteFile 接收文件–io.Copy

    2024年02月06日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包