Go语言入门(七)goroutine和channel

goroutine和channel

goroutine

多线程

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

func main() {
   go hello()    //启动了一个独立的线程,使其与下面的代码交替执行,使之成为一个多线程
   //fmt.Printf("main function\n")
   for i:=0;i<100;i++ {
      fmt.Printf("main:%d\n",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("%d\n",i)
   }
}

func chars() {
   for i:=‘a‘;i<=‘e‘;i++ {
      time.Sleep(time.Millisecond*400)
      fmt.Printf("%c\n",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("%d\n",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:%p\n",intChan)
   go func() {
      //写入数据
      intChan <- 100
      fmt.Printf("insert item end\n")
   }()

   go func() {
      //读取数据
      fmt.Printf("start\n")
      time.Sleep(time.Second*3)
      var a int
      a = <- intChan
      fmt.Printf("intChan:%d\n",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=%s\n",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 %d\n",i)
      fmt.Printf("write:%s\n",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:%s\n",input)
   }
   exitCh <- true
}

func getChannels2(ch chan string,exitCh chan bool) {
   for {
      // 检查channel关闭
      input,ok := <- ch
      if !ok {
         break
      }
      fmt.Printf("getchannels中的input:%s\n",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 exited\n")
   waitGroup.Done()    //结束goroutine的标志,然后对标志位的数字-1
}

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

func GetChannels2(ch chan string,waitGroup *sync.WaitGroup) {
   for {
      input,ok := <- ch
      if !ok{
         break
      }
      fmt.Printf("getchannels中的input:%s\n",input)
   }
   fmt.Printf("get data2 exited\n")
   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 exited\n")
}
  • 关闭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=%d\n",a)
   }
}
  • channel的遍历
func GetChannels2(ch chan string,waitGroup *sync.WaitGroup) {
   //for range会自动判断channel是否关闭
   for v :=range ch {
      fmt.Printf("get data2 %s\n",v)
   }

   fmt.Printf("get data2 exited\n")
   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 succ\n")
         case strChan <- "hello":
            fmt.Printf("write to str chan succ\n")
         default:
            fmt.Printf("all chan is full\n")
            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:%d\n",a)
         case <- strChan:
            fmt.Printf("read from str chan\n")
         default:
            fmt.Printf("all chan is empty\n")
            time.Sleep(time.Second)
         }
      }
      wg.Done()
   }()
   wg.Wait()
}

原文地址:https://blog.51cto.com/13812615/2483168

时间: 2024-10-07 14:46:24

Go语言入门(七)goroutine和channel的相关文章

go语言系列-从Goroutine到Channel

Golang语言的核心特色 目录 Goroutine 基本介绍 进程和线程介绍 程序.进程和线程的关系示意图 并发和并行 Go协程和Go主线程 快速入门 案例说明 小结 goroutine的调度模型 MPG模式运行的状态 -1 MPG模式运行的状态 - 2 设置Go运行的CPU数 Channel(管道) 看个需求 不同goroutine之间如何通讯 使用全局变量加锁同步改进程序 为什么需要channel channel的基本介绍 定义/声明channel 管道的初始化.写入数据到管道.从管道读取

TODO:Go语言goroutine和channel使用

goroutine是Go语言中的轻量级线程实现,由Go语言运行时(runtime)管理.使用的时候在函数前面加"go"这个单词作为关键词,也是与普通函数的区别了.在函数前面加go关键字就可以创建一个新的goroutine进行并发执行. go hello() channel是Go语言提供的goroutine间的通信方式,我们可以使用channel在两个或多个goroutine之家传递消息.channel使用的关键字是用"chan",声明一个传递类型为int的chann

【南阳OJ分类之语言入门】80题题目+AC代码汇总

声明: 题目部分皆为南阳OJ题目. 代码部分包含AC代码(可能不止一个)和最优代码,大部分都是本人写的,并且大部分为c代码和少部分c++代码and极少java代码,但基本都是c语言知识点,没有太多差别,可能代码有的写的比较丑,毕竟知识有限. 语言入门部分题基本都较为简单,是学习编程入门的很好练习,也是ACM的第一步,入门的最佳方法,望认真对待. 本文由csdn-jtahstu原创,转载请注明出处,欢迎志同道合的朋友一起交流学习.本人QQ:1373758426和csdn博客地址. now begi

Python爬虫入门七之正则表达式

在前面我们已经搞定了怎样获取页面的内容,不过还差一步,这么多杂乱的代码夹杂文字我们怎样把它提取出来整理呢?下面就开始介绍一个十分强大的工具,正则表达式! 1.了解正则表达式 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符.及这些特定字符的组合,组成一个"规则字符串",这个"规则字符串"用来表达对字符串的一种过滤逻辑. 正则表达式是用来匹配字符串非常强大的工具,在其他编程语言中同样有正则表达式的概念,Python同样不例外,利用了正则表达式,我

转 Python爬虫入门七之正则表达式

静觅 » Python爬虫入门七之正则表达式 1.了解正则表达式 正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符.及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑. 正则表达式是用来匹配字符串非常强大的工具,在其他编程语言中同样有正则表达式的概念,Python同样不例外,利用了正则表达式,我们想要从返回的页面内容提取出我们想要的内容就易如反掌了. 正则表达式的大致匹配过程是:1.依次拿出表达式和文本中的字符比较,2.如果每一个

C#基础入门 七

C#基础入门 七 接口 由于C#语言不支持多重继承,所以可以使用接口模拟结构的继承,通过使用interface关键字,定义一个接口. interface USB { void Read(string[] datas); } 接口与抽象类非常类似,它定义了一些未实现的属性和方法,所有继承它的类都继承这些成员,在这个角度上,可以把接口理解为一个类的模板,接口最终的目的是起到统一的作用. 实现接口的任何类或结构必须实现其所有成员的方法: 接口不能直接实例化,但是可以通过指向子类间接实例化: 接口可以包

Go 语言入门(三)并发

写在前面 在学习 Go 语言之前,我自己是有一定的 Java 和 C++ 基础的,这篇文章主要是基于A tour of Go编写的,主要是希望记录一下自己的学习历程,加深自己的理解 Go 语言入门(三)并发 Go 程 「Go 程」goroutine:由 Go 运行时管理的轻量级线程. 运行「Go 程」很简单,只要执行下面代码: go f(x, y, z) 就会启动一个新的 Go 程并执行f(x, y, z).f.x.y和z的运算发生在当前的 Go 程中,而f的执行发生在新的 Go 程中. 「Go

C语言入门(二十五)文件操作

文件操作  一.标准文件的读写 1.文件的打开fopen() 文件的打开操作表示将给用户指定的文件在内存分配一个FILE结构区,并将该结构的指针返回给用户程序,以后用户程序就可用此FILE指针来实现对指定文件的存取操作了.当使用打开函数时,必须给出文件名.文件操作方式(读.写或读写),如果该文件名不存在,就意味着建立(只对写文件而言,对读文件则出错),并将文件指针指向文件开头.若已有一个同名文件存在,则删除该文件,若无同名文件,则建立该文件,并将文件指针指向文件开头. fopen(char *f

第一节,C语言入门

1.标示符:    命名规则:    1.只能由字母.数字.下划线组成    2.不能数字开头    3.不能与关键字重名    4.严格区分大小写    命名规范:     1.起一个有意义名字     2.驼峰标示2.注释 注释: 对代码的解释说明,是写给程序看的,方面程序员之间交流 特点: 注释是不参与编译 /* 这里面可以写 */ 多行注释 // 这是一个单行注释 只有这一行是注释,只有 两个斜杠后面才是注释内容 /* */ command + / 注释或取消注释 多行注释是可以嵌套单行