Go语言入门(七)goroutine和channel

时间:2022-07-25
本文章向大家介绍Go语言入门(七)goroutine和channel,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

goroutine和channel

goroutine

多线程

func hello() {
   //fmt.Printf("Hello Goroutine!!n")
   for i:=0;i<100;i++ {
      fmt.Printf("hello:%dn",i)
      time.Sleep(time.Millisecond)
   }
}

func main() {
   go hello()    //启动了一个独立的线程,使其与下面的代码交替执行,使之成为一个多线程
   //fmt.Printf("main functionn")
   for i:=0;i<100;i++ {
      fmt.Printf("main:%dn",i)
      time.Sleep(time.Millisecond)
   }
   time.Sleep(time.Second)   //修复代码,使得主线程退出的时候子线程能执行
}

多个goroutine

func nunmers() {
   for i :=0;i<=5;i++ {
      time.Sleep(time.Millisecond*250)
      fmt.Printf("%dn",i)
   }
}

func chars() {
   for i:='a';i<='e';i++ {
      time.Sleep(time.Millisecond*400)
      fmt.Printf("%cn",i)
   }
}

func main() {
   go nunmers()
   go chars()
   time.Sleep(time.Second*3)
}

进程和线程

  • 进程:
    • 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位
  • 线程:
    • 线程是进程的一个执行实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位
  • 一个进程可以创建和撤销多个线程,同一个进程中的多个线程之间可以并发执行

并发与并行

  • 多线程程序在一个核的CPU上运行,这是并发
  • 多线程程序在多个核的CPU上运行,这是并行

协程和线程

  • 协程: 独立的栈空间,共享堆空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的
  • 线程: 一个县城上可以跑多个协程,协程是轻量级的线程

goroutine的调度模型

  • M(线程)P(上下文)G(goroutine)

设置golang运行的CPU核心数

func main() {
   cpu := runtime.NumCPU()
   //限制核心数(新版本不用考虑),比如监控程序,可以控制其运行的资源消耗
   //runtime.GOMAXPROCS(1)
   for i:=0;i <=8;i++ {
      go func() {
      }()
   }
   fmt.Printf("%dn",cpu)
   time.Sleep(time.Second*12)
}

channel

  • 不同的goroutine之间如何进行通讯:
    • 全局变量和锁同步: 不推荐
    • Channel: 先进先出的队列
  • channel的概念
    • 类似于unix中的管道
    • 先进先出
    • 线程安全,多个goroutine同事访问不需要加锁
    • channel是有类型的,一个证书的channel只能整数

channel的声明

  • 引用类型,需要使用make来初始化
var 变量名 chan 类型
var test chan int
var test chan string
var test chan map[string]string
var test chan stu
var test chan *stu
  • channel的初始化与读写
func main() {
   //var intChan chan int = make(chan int,1)   有缓冲区的channel
   var intChan chan int = make(chan int)      // 无缓冲区channel
   fmt.Printf("intChanin:%pn",intChan)
   go func() {
      //写入数据
      intChan <- 100
      fmt.Printf("insert item endn")
   }()

   go func() {
      //读取数据
      fmt.Printf("startn")
      time.Sleep(time.Second*3)
      var a int
      a = <- intChan
      fmt.Printf("intChan:%dn",a)
   }()
   time.Sleep(time.Second*5)
}

goroutine和channel相结合

  • 生产者消费者模型
func senData(ch chan string) {
   ch <- "Washinton"
   ch <- "Tripoli"
   ch <- "LongDong"
   ch <- "Beijing"
   ch <- "Tokyo"
}

func getData(ch chan string) {
   var input string
   for {
      input = <- ch
      fmt.Printf("input=%sn",input)
   }
}

func main() {
   ch := make(chan string,10)
   go  senData(ch)
   go getData(ch)
   time.Sleep(time.Second*1)
}

阻塞channel的情况

  • 无写入,空读取会阻塞(空channel)
  • 写入的数量超过缓冲区也会阻塞 (channel满了)
func sendChans(ch chan string)  {
   var i int
   for {
      var str string
      str = fmt.Sprintf("stu %dn",i)
      fmt.Printf("write:%sn",str)
      //无人消费数据,则阻塞,如果无缓冲区的话,写入也是阻塞的
      ch <- str
      i++
   }
}

func main() {
   ch := make(chan string,10)   //带缓冲区
   //ch := make(chan string)   //无缓冲区
   go sendChans(ch)
   time.Sleep(time.Second*200)
}

channel之间的同步

  • 新增退出检测的方式保证同步
func sendChannels(ch chan string,exitCh chan bool) {
   ch <- "AA"
   ch <- "BB"
   ch <- "CC"
   ch <- "DD"
   ch <- "EE"
   close(ch)
   exitCh <- true
}

func getChannels(ch chan string,exitCh chan bool) {
   for {
      // 检查channel关闭
      input,ok := <- ch
      if !ok {
         break
      }
      fmt.Printf("getchannels中的input:%sn",input)
   }
   exitCh <- true
}

func getChannels2(ch chan string,exitCh chan bool) {
   for {
      // 检查channel关闭
      input,ok := <- ch
      if !ok {
         break
      }
      fmt.Printf("getchannels中的input:%sn",input)
   }
   exitCh <- true
}

func main() {
   ch := make(chan string)
   exitChan := make(chan bool,3)
   go sendChannels(ch,exitChan)
   go getChannels(ch,exitChan)
   go getChannels2(ch,exitChan)
   //三次检查状态,然后退出,不需要time.sleep,等待其他goroutine退出
   <- exitChan    //取出元素,然后扔掉
   <- exitChan
   <- exitChan
}
  • 使用waitGroup的方式确保goroutine的同步
func SendChannels1(ch chan string,waitGroup *sync.WaitGroup) {
   ch <- "AA"
   ch <- "BB"
   ch <- "CC"
   ch <- "DD"
   ch <- "EE"
   close(ch)
   fmt.Printf("send data exitedn")
   waitGroup.Done()    //结束goroutine的标志,然后对标志位的数字-1
}

func GetChannels1(ch chan string,waitGroup *sync.WaitGroup) {
   for {
      input,ok := <- ch
      if !ok{
         break
      }
      fmt.Printf("getchannels中的input:%sn",input)
   }
   fmt.Printf("get data exitedn")
   waitGroup.Done()
}

func GetChannels2(ch chan string,waitGroup *sync.WaitGroup) {
   for {
      input,ok := <- ch
      if !ok{
         break
      }
      fmt.Printf("getchannels中的input:%sn",input)
   }
   fmt.Printf("get data2 exitedn")
   waitGroup.Done()
}

func main() {
   var wg sync.WaitGroup
   ch := make(chan string)
   wg.Add(3)  //每执行完一次goroutine就会-1
   go SendChannels1(ch,&wg)
   go GetChannels1(ch,&wg)
   go GetChannels2(ch,&wg)
   wg.Wait()   //等到运行完成之后返回
   fmt.Printf("main goroutine exitedn")
}
  • 关闭channel的时候,生产者channel的数据是保存的,不丢失
func main() {
   intChan := make(chan int,10)
   for i:=0;i<10;i++ {
      intChan<- i
   }
   //关闭channel
   close(intChan)
   time.Sleep(time.Second)
   for j:=0;j<10;j++ {
      a := <- intChan
      fmt.Printf("a=%dn",a)
   }
}
  • channel的遍历
func GetChannels2(ch chan string,waitGroup *sync.WaitGroup) {
   //for range会自动判断channel是否关闭
   for v :=range ch {
      fmt.Printf("get data2 %sn",v)
   }

   fmt.Printf("get data2 exitedn")
   waitGroup.Done()
}
  • chan的关闭
    • 使用内置函数close进行关闭,chan关闭之后,for range 遍历chan中已经存在的元素后结束
    • 使用内置函数close进行关闭,chan关闭之后,没有使用for range的写法,需要使用v,ok :=&lt;-chan判断chan是否关闭

chan的只读和只写

需要注意&lt;-是在chan关键字的位置,&lt;-chan左侧表示只读,在右侧表示只写

func chanPerms() {
   var readOnly <- chan int = make(chan int,100)
   // readOnly <- 100 只读不可写
   var writeOnly chan <- int = make(chan int,10)
   // <- writeOnly   只写不可读
}
  • 使用场景: 三方调用只读只写权限控制,防止误操作

对chan进行select操作

func main() {
   var intChan chan int = make(chan int,10)
   var strChan chan string = make(chan string,10)
   var wg sync.WaitGroup
   wg.Add(2)
   // 插入数据
   go func() {
      var count int
      for count < 1000 {
         count++
         select {
         case intChan <- 10:
            fmt.Printf("write to int chan succn")
         case strChan <- "hello":
            fmt.Printf("write to str chan succn")
         default:
            fmt.Printf("all chan is fulln")
            time.Sleep(time.Second)
         }
      }
      wg.Done()
   }()
   wg.Wait()

   //读取数据
   go func() {
      var count int
      for count < 10000 {
         count ++
         select {
         case a := <- intChan:
            fmt.Printf("read from int chain a:%dn",a)
         case <- strChan:
            fmt.Printf("read from str chann")
         default:
            fmt.Printf("all chan is emptyn")
            time.Sleep(time.Second)
         }
      }
      wg.Done()
   }()
   wg.Wait()
}