Go的学习旅程6:并发与通信

1.并发

goroutine是Go并行设计的核心。goroutine说到底其实就是协程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。

goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过go关键字实现了,其实就是一个普通的函数。

package main

import (
   "fmt"
   "runtime"
   "time"
)

func say(s string){
   for i:=0;i<10 ;i++  {
      //runtime.Gosched()表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine      runtime.Gosched()
      fmt.Println(s)
   }
}

func main() {
   //并行执行
   for i:=0;i<10 ;i++  {
      go func(i int) {
         fmt.Println("我在并发执行:",i)
      }(i)
   }
   //休息1   time.Sleep(time.Millisecond*1000)
   //普通执行
   for i:=0;i<10 ;i++  {
      fmt.Println("我在并发执行:",i)
   }
   
   go say("你好")

   say("hello")

}


结果展示:



2.通信

goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel.

所有的通信,都是基于并发也就是goroutine来做的


2.1 Range,Close以及channel的基础用法

package main

import (
   "fmt"
   "time"
)

//简单的写法
func aaa() {
   //创建channel(用于协程的通讯)
   a := make(chan int)
   go func() {
      for c := range a {
         //channel通过操作符<-来接收和发送数据
         //c接收a的消息
         fmt.Println(c)
      }

   }()
   //channel通过操作符<-来接收和发送数据
   //发送1,2,3a
   a <- 1
   a <- 2
   a <- 3
   time.Sleep(time.Millisecond * 500)
}

//封装的写法
func bbb() {
   a := make(chan int)
   go worker(a)

   a <- 4
   a <- 5
   a <- 6
   close(a)
   time.Sleep(time.Millisecond * 500)
}

func worker(c chan int) {
   //判断剔除空数据
   for n := range c {
      fmt.Println(n)
   }
}

//创建多个通讯
func ccc() {
   var a [10]chan int
   for i := 0; i < 10; i++ {
      a[i] =  workk(i)
   }
   for i := 0; i < 10; i++ {
      a[i] <- 'a' + i
      //发送方给接收方断开,close的发送值是空,需要在接收方里面判断是否为空
      close(a[i])
   }
   time.Sleep(time.Millisecond)
}

func workk(i int) chan int {
   c := make(chan int)
   //方法一
   //go func() {
   // if n, ok := <-c; ok {
   //    fmt.Printf("id: %d,通讯是: %c \n", i, n)
   // }
   //}()
   //方法二
   go func() {
      for n := range c {
         fmt.Printf("id: %d,通讯是: %c \n", i, n)
      }
   }()
   return c
}
func main() {
   //aaa()
   //bbb()
   //ccc()

}

        在封装bbb的写法中,我们一般的情况是如下这种,先让a<-4,5,6;完后再回取出,但是这种情况往往报错,我们可以解析一下代码运行的情况:    

         1.  创建了一个无缓冲的channel

        2.  主routine要向channel中放入一个数据,但是因为channel没有缓冲,相当于channel一直都是满的,所以这里会发生阻塞。可是下面的那个goroutine还没有创建呢,主routine在这里一阻塞,整个程序就只能这么一直阻塞下去了,然后。。。然后就没有然后了。。死锁!

※从这里可以看出,对于无缓冲的channel,放入操作和取出操作不能再同一个routine中,而且应该是先确保有某个routine对它执行取出操作,然后才能在另一个routine中执行放入操作。

func bbb() {
   a := make(chan int)
   a <- 4
   a <- 5
   a <- 6
  go worker(a)
close( a) time. Sleep(time.Millisecond * 500)}


结果展示:



2.2 channel任务的结束

在上面的例子中,使用的是time.Sleep()而实现协程中通讯的输出,而以下例子中有两种方法:

    1.利用通信,完成任务的回调

    2.利用sync包来解决协程间的同步与通信

package main

import (
   "fmt"
   "sync"
)

type work struct {
   i  chan int
   dn chan bool
}

//创建多个通讯,利用通信来共享内存
func ccc() {
   var a [10]work
   for i := 0; i < 10; i++ {
      a[i] = workk(i)
   }
   for i, v := range a {
      v.i <- 'a' + i
      // 取出数据 (通过 <-channel 操作)
      //<-a[i].dn //返回true
   }
   for i, v := range a {
      // 取出数据 (通过 <-channel 操作)
      <-v.dn
      fmt.Printf("我的编号是%d \n", i)
   }
   for i, v := range a {
      v.i <- 'A' + i
      // 取出数据 (通过 <-channel 操作)
      //<-a[i].dn //返回true
   }
   for i, v := range a {
      // 取出数据 (通过 <-channel 操作)
      <-v.dn
      fmt.Printf("我的编号是%d \n", i)
   }
}

func workk(i int) work {
   w := work{
      i:  make(chan int),
      dn: make(chan bool),
   }
   go func() {
      for n := range w.i {
         fmt.Printf("id: %d,通讯是: %c \n", i, n)
         //放入数据,如果两个chan一起写报错,原因是w.dn <- true被阻塞,没有消耗
         w.dn <- true
         //go func() {w.dn <- true}()
      }
   }()
   return w
}

//利用go包中的WaitGroup实现队列
type wg_sync struct {
   i  chan int
   wg *sync.WaitGroup
}

func ddd() {
   var wg sync.WaitGroup

   var a [10]wg_sync
   wg.Add(20)
   for i := 0; i < 10; i++ {
      a[i] = wg_work(i, &wg)
   }
   for i, v := range a {
      v.i <- 'a' + i
      // 取出数据 (通过 <-channel 操作)
      //<-a[i].dn //返回true
   }
   for i, v := range a {
      v.i <- 'A' + i
      // 取出数据 (通过 <-channel 操作)
      //<-a[i].dn //返回true
   }
   wg.Wait()
}

func wg_work(i int, wg *sync.WaitGroup) wg_sync {  
   w := wg_sync{
      i:  make(chan int),
      wg: wg,
   }
   go func() {
      for n := range w.i {
         fmt.Printf("id: %d,通讯是: %c \n", i, n)
         w.wg.Done()
      }
   }()
   return w
}

func main() {
   //fmt.Println("我是ccc")
   //ccc()
   fmt.Println("我是ddd")
   ddd()

}


结果展示:



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值